29436 2020-04-02 2020-09-12
前言:Spring MVC系列收尾篇,本篇将详细讨论Spring MVC的启动/加载、处理请求的具体流程。我们先从一个简单的Spring MVC日志开始分析(这里假设读者已经仔细阅读完了前面的文章,且对Spring源码有一定深度的了解,否则会看得一脸闷逼)。
一、配置
1、log4j配置
先附上日志配置,如下
log4j.rootLogger=trace, stdout
log4j.logger.org.springframework=trace, stdout
# 去掉和rootLogger重合的部分
log4j.additivity.org.springframework=false
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# 日期 线程名(保留后3位) 日志级别 类路径(只保留最后2个父包) 行号 输出信息 换行
log4j.appender.stdout.layout.conversionPattern=%d{HH:mm:ss.SSS} [%.3t][%p] %C{3} %L: %m%n
2、web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- <servlet-class>xiaokui.test.TestServlet</servlet-class>-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3、applicationContext.xml
<mvc:annotation-driven/>
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
<context:component-scan base-package="xiaokui.*"/>
4、dispatcher-servlet.xml
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
其他bean我们并不关心,大致有个Controller、Service、Dao结构就行了。
下面将一行行、一段段进行解读一个真实Spring MVC项目的日志输出(中间没有任何断层,且为了行文方便,故意拆分为多个逻辑)。
二、applicationContext启动流程概览
我们先来看下由applicationContext.xml搞出来的事情。
# 如果有部分术语看不懂,请先查看之前的文章,这里假设读者已经对Spring MVC内部已经有了一个大概的了解了
# 通过ContextLoaderListener监听,开始刷新WebApplicationContext
# 注释采取python格式,因为# 比 // 好打
# 监听器成功引导了Spring IoC容器
10:34:58.340 [0.1][INFO] web.context.ContextLoader 271: Root WebApplicationContext: initialization started
10:34:58.413 [0.1][TRACE] context.support.AbstractApplicationContext 592: Refreshing Root WebApplicationContext, started on Thu May 07 10:34:58 CST 2020
# 把注意了转移到了对applicationContext的解析,里面定义个整个项目中对WebApplicationContext的配置
10:34:58.475 [0.1][TRACE] factory.xml.XmlBeanDefinitionReader 318: Loading XML bean definitions from ServletContext resource [/WEB-INF/applicationContext.xml]
10:34:58.477 [0.1][TRACE] factory.xml.DefaultDocumentLoader 74: Using JAXP provider [com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl]
10:34:58.511 [0.1][TRACE] factory.xml.PluggableSchemaResolver 111: Trying to resolve XML entity with public id [null] and system id [http://www.springframework.org/schema/beans/spring-beans.xsd]
10:34:58.512 [0.1][TRACE] factory.xml.PluggableSchemaResolver 154: Loading schema mappings from [META-INF/spring.schemas]
10:34:58.516 [0.1][TRACE] factory.xml.PluggableSchemaResolver 160: Loaded schema mappings: {https://www.springframework.org/schema/jdbc/spring-jdbc.xsd=org/springframework/jdbc/config/spring-jdbc.xsd, https://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop.xsd, https://www.springframework.org/schema/tx/spring-tx-4.3.xsd=org/springframework/transaction/config/spring-tx.xsd.....以下省略50行
10:34:58.520 [0.1][TRACE] factory.xml.PluggableSchemaResolver 128: Found XML schema [http://www.springframework.org/schema/beans/spring-beans.xsd] in classpath: org/springframework/beans/factory/xml/spring-beans.xsd.....以下省略50行
10:34:58.623 [0.1][TRACE] factory.xml.DefaultNamespaceHandlerResolver 160: Loading NamespaceHandler mappings from [META-INF/spring.handlers]
# 已经找到了对于的自定义标签的NamespaceHandler,它们都继承自NamespaceHandlerSupport(相关知识请参考xml自定义标签解析一文)
10:34:58.625 [0.1][TRACE] factory.xml.DefaultNamespaceHandlerResolver 166: Loaded NamespaceHandler mappings: {http://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler, http://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler, http://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler, http://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler, http://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler, http://www.springframework.org/schema/oxm=org.springframework.oxm.config.OxmNamespaceHandler, http://www.springframework.org/schema/jdbc=org.springframework.jdbc.config.JdbcNamespaceHandler, http://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler, http://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler, http://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler, http://www.springframework.org/schema/jms=org.springframework.jms.config.JmsNamespaceHandler, http://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler, http://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler, http://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler}
通过debug可以发现(在其公共父类上NamespaceHandlerSupport打上断点即可),Spring解析xml是从上往下依次解析的,在这里第一个被解析的是aop标签,虽然这里的解析顺序并不会影响解析的正确结果。
1、引导
这里我们简单概述一下,好对整个流程有个大概的了解,更多细节请参考前面的相关文章:
- Tomcat容器启动,web.xml配置项入口。
- ContextLoaderListener作为ServeletContext初始化的监听器,在Servlet环境初始化后会进入到这个类,这个类会创建Spring中的IoC容器,并根据context-param加载applicationContext.xml。
- 在applicationContext.xml中的RootContext初始化后,然后再由Servlet的配置去初始化DispatcherServlet,这里会按照相同的流程初始化dispatcher-servlet.xml,这里是ChildContext。
- ServletContext、DispatcherServlet初始化完成,Tomcat成功启动。
每次Context的初始化,都是以wac.refresh结尾的,这里面包含了Spring的核心逻辑,我们选取几个比较典型的关注点:
- 什么时候真正加载xml配置文件 - obtainFreshBeanFactory方法。
- 什么时候开始自定义标签的解析,如aop、context、mvc等自定义标签的解析 - obtainFreshBeanFactory。
- @Configuration注解类何时应用 - invokeBeanFactoryPostProcessors方法中的ConfigurationClassPostProcessor类负责解析。
- @Component注解类何时应用 - finishBeanFactoryInitialization方法。
- @Autowired、@Value注解何时应用 - registerBeanPostProcessors方法中的AutowiredAnnotationBeanPostProcessor负责解析。
- 动态代理何时应用 - finishBeanFactoryInitialization方法中AnnotationAwareAspectJAutoProxyCreator负责筛选。
2、wac.refresh**
refresh方法算是Context中的发动机了,重中之重。如果把Spring IoC当做人体中的骨架 + 躯干,各个子Bean比作血液 + 各个器官,那么Context中的refresh方法同大脑 + 心脏,统筹全局,精打细算,其重要性不言而喻(比喻可能不是很恰当,读者知有这么个概念即可,详情读者可参考前面的相关文章)。
// 第一次RootContext的刷新
// 来自父类AbstractApplicationContext
// 注意,这里分析就不再以ClassPathXmlApplicationContext,而是以XmlWebApplicationContext,两者大致流程是一样的,区别后者专门是为Web服务的
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 准备刷新的上下文环境,初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证。
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
// 告诉子类刷新内部bean工厂,初始化DefaultListableBeanFactory,并进行xml文件读取
// 复用BeanFactory中的配置文件读取解析及其他功能,这一步之后,ClassPathXMLApplicationContext实际上已经包含了BeanFactory所提供的功能,也就是可以进行bean的提取等基础操作了
// 这一步之后,实际上已经读取了aop标签内置的bean、mvc标签内置的bean以及context标签所扫描的需要bean
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 对BeanFactory进行各种功能填充,例如增加对SPEL语言的支持、增加对属性编辑器的支持、设置了依赖功能可忽略的接口、注册一些固定依赖的属性、将相关环境变量及属性注册以单例模式注册
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
// 子类覆盖方法做前置处理
// web重写:Register request/session scopes, a {@link ServletContextAwareProcessor}, etc.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 激活各种BeanFactory处理器,针对BeanFactory做定制化,主要是BeanFactoryPostProcessor接口及其的子接口类的特殊处理
// 区别于BeanFactoryPostProcessors,而BeanPostProcessors针对于bean的
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
// 注册拦截bean创建的bean处理器,这里只是注册,真正的调用是在getBean的时候
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
// 初始化应用消息广播器,并放入applicationEventMulticaster bean中
initMessageSource();
// Initialize event multicaster for this context.
// 留个子类来初始化其他的bean
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
// 留个子类来初始化其他的bean
onRefresh();
// Check for listener beans and register them.
// 在所有注册的bean种查找listener bean,注册到消息广播中
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
// 初始化剩下的单实例(非惰性)
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
// 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
三、obtainFreshBeanFactory与applicationContext.xml
1、概述
在Tomcat容器成功引导了Spring IoC容器,即成功初始化了一个DefaultListableBeanFactory对象,后续就是如何找到Bean对象,如何将Bean对象放置进容器,如何从容器取出的过程了。
从前文得知,在Spring MVC中的ServletContext刷新的最后一步就是调用wac.refresh方法,而其中wac.refresh方法第一步就是加载applicationContext.xml,获取所有系统内置bean、用户bean,于是开始了自定义标签的解析过程了。
2、aop
1、关键代码
/**
* {@code NamespaceHandler} for the {@code aop} namespace.
*
* <p>Provides a {@link org.springframework.beans.factory.xml.BeanDefinitionParser} for the
* {@code <aop:config>} tag. A {@code config} tag can include nested
* {@code pointcut}, {@code advisor} and {@code aspect} tags.
*
* <p>The {@code pointcut} tag allows for creation of named
* {@link AspectJExpressionPointcut} beans using a simple syntax:
* <pre class="code">
* <aop:pointcut id="getNameCalls" expression="execution(* *..ITestBean.getName(..))"/>
* </pre>
*
* <p>Using the {@code advisor} tag you can configure an {@link org.springframework.aop.Advisor}
* and have it applied to all relevant beans in you {@link org.springframework.beans.factory.BeanFactory}
* automatically. The {@code advisor} tag supports both in-line and referenced
* {@link org.springframework.aop.Pointcut Pointcuts}:
*
* <pre class="code">
* <aop:advisor id="getAgeAdvisor"
* pointcut="execution(* *..ITestBean.getAge(..))"
* advice-ref="getAgeCounter"/>
*
* <aop:advisor id="getNameAdvisor"
* pointcut-ref="getNameCalls"
* advice-ref="getNameCounter"/></pre>
*
*/
public class AopNamespaceHandler extends NamespaceHandlerSupport {
/**
* Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
* '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
* and '{@code scoped-proxy}' tags.
*/
@Override
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace as of 2.1
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
/**
* {@link BeanDefinitionParser} for the {@code aspectj-autoproxy} tag,
* enabling the automatic application of @AspectJ-style aspects found in
* the {@link org.springframework.beans.factory.BeanFactory}.
*
*/
class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 这里会注入一个AnnotationAwareAspectJAutoProxyCreator,用于支持AspectJ语法的动态代理
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
extendBeanDefinition(element, parserContext);
return null;
}
}
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
// 注册BeanDefinition
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
// 设置BeanDefinition中的相关设置属性
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
// 注册xml组件
registerComponentIfNecessary(beanDefinition, parserContext);
}
2、小结
更为详细的讨论请参考Spring AOP一节,这里简单提一下:AnnotationAwareAspectJAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor接口,并重写对应方法以使得当发现一个bean需要代理时,会返回创建的代理对象,而不是创建一个新的原Class类型的对象。关键代码如下
// 来源于AnnotationAwareAspectJAutoProxyCreator的父类 AbstractAutoProxyCreator
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 该bean是否符合Aspect或自定义的代理规则,以决定是否需要代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
这一行代码<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
,注册了一个AnnotationAwareAspectJAutoProxyCreator,让Spring支持动态代理,例如对@AspectJ、@After、@Before、@Around的支持。这里隐式注册的bean只有1个,如下
org.springframework.aop.config.internalAutoProxyCreator -> {RootBeanDefinition@3705} "Root bean: class [org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
3、mvc
1、关键代码
// 通过前面日志我们定位到了MvcNamespaceHandler
/**
* {@link NamespaceHandler} for Spring MVC configuration namespace.
*/
public class MvcNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 这一行
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
}
}
/**
* A {@link BeanDefinitionParser} that provides the configuration for the
* {@code <annotation-driven/>} MVC namespace element.
*
* <p>This class registers the following {@link HandlerMapping HandlerMappings}:</p>
* <ul>
* <li>{@link RequestMappingHandlerMapping}
* ordered at 0 for mapping requests to annotated controller methods.
* <li>{@link BeanNameUrlHandlerMapping}
* ordered at 2 to map URL paths to controller bean names.
* </ul>
*
* <p><strong>Note:</strong> Additional HandlerMappings may be registered
* as a result of using the {@code <view-controller>} or the
* {@code <resources>} MVC namespace elements.
*
* <p>This class registers the following {@link HandlerAdapter HandlerAdapters}:
* <ul>
* <li>{@link RequestMappingHandlerAdapter}
* for processing requests with annotated controller methods.
* <li>{@link HttpRequestHandlerAdapter}
* for processing requests with {@link HttpRequestHandler HttpRequestHandlers}.
* <li>{@link SimpleControllerHandlerAdapter}
* for processing requests with interface-based {@link Controller Controllers}.
* </ul>
*
* <p>This class registers the following {@link HandlerExceptionResolver HandlerExceptionResolvers}:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through
* {@link org.springframework.web.bind.annotation.ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated
* with {@link org.springframework.web.bind.annotation.ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring
* exception types
* </ul>
*
* <p>This class registers an {@link org.springframework.util.AntPathMatcher}
* and a {@link org.springframework.web.util.UrlPathHelper} to be used by:
* <ul>
* <li>the {@link RequestMappingHandlerMapping},
* <li>the {@link HandlerMapping} for ViewControllers
* <li>and the {@link HandlerMapping} for serving resources
* </ul>
* Note that those beans can be configured by using the {@code path-matching}
* MVC namespace element.
*
* <p>Both the {@link RequestMappingHandlerAdapter} and the
* {@link ExceptionHandlerExceptionResolver} are configured with instances of
* the following by default:
* <ul>
* <li>A {@link ContentNegotiationManager}
* <li>A {@link DefaultFormattingConversionService}
* <li>A {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean}
* if a JSR-303 implementation is available on the classpath
* <li>A range of {@link HttpMessageConverter HttpMessageConverters} depending on which third-party
* libraries are available on the classpath.
* </ul>
*/
// 通过以上注释我们知道Spring帮我们内置了一大堆web组件
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
public static final String HANDLER_MAPPING_BEAN_NAME = RequestMappingHandlerMapping.class.getName();
public static final String HANDLER_ADAPTER_BEAN_NAME = RequestMappingHandlerAdapter.class.getName();
public static final String CONTENT_NEGOTIATION_MANAGER_BEAN_NAME = "mvcContentNegotiationManager";
private static final boolean javaxValidationPresent;
private static boolean romePresent;
private static final boolean jaxb2Present;
private static final boolean jackson2Present;
private static final boolean jackson2XmlPresent;
private static final boolean jackson2SmilePresent;
private static final boolean jackson2CborPresent;
private static final boolean gsonPresent;
static {
// 判断某个类是否存在,类boot思想,约定大于配置
ClassLoader classLoader = AnnotationDrivenBeanDefinitionParser.class.getClassLoader();
javaxValidationPresent = ClassUtils.isPresent("javax.validation.Validator", classLoader);
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
}
@Override
@Nullable
// 我们稍稍过下这个方法,尝试去寻找我们需要关注的点
public BeanDefinition parse(Element element, ParserContext context) {
Object source = context.extractSource(element);
XmlReaderContext readerContext = context.getReaderContext();
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
context.pushContainingComponent(compDefinition);
RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, context);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("order", 0);
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
if (element.hasAttribute("enable-matrix-variables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}
configurePathMatchingProperties(handlerMappingDef, element, context);
readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);
RuntimeBeanReference conversionService = getConversionService(element, source, context);
RuntimeBeanReference validator = getValidator(element, source, context);
RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
bindingDef.setSource(source);
bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
bindingDef.getPropertyValues().add("conversionService", conversionService);
bindingDef.getPropertyValues().add("validator", validator);
bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
ManagedList<?> messageConverters = getMessageConverters(element, source, context);
ManagedList<?> argumentResolvers = getArgumentResolvers(element, context);
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, context);
String asyncTimeout = getAsyncTimeout(element);
RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
ManagedList<?> callableInterceptors = getInterceptors(element, source, context, "callable-interceptors");
ManagedList<?> deferredResultInterceptors = getInterceptors(element, source, context, "deferred-result-interceptors");
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
addRequestBodyAdvice(handlerAdapterDef);
addResponseBodyAdvice(handlerAdapterDef);
if (element.hasAttribute("ignore-default-model-on-redirect")) {
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
}
if (argumentResolvers != null) {
handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
if (asyncTimeout != null) {
handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
}
if (asyncExecutor != null) {
handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
}
handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
RootBeanDefinition uriContributorDef =
new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
uriContributorDef.setSource(source);
uriContributorDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
uriContributorDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
String uriContributorName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
readerContext.getRegistry().registerBeanDefinition(uriContributorName, uriContributorDef);
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
csInterceptorDef.setSource(source);
csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedInterceptorDef.setSource(source);
mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
methodExceptionResolver.setSource(source);
methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
methodExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyAdvice(methodExceptionResolver);
if (argumentResolvers != null) {
methodExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
methodExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);
RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
statusExceptionResolver.setSource(source);
statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
statusExceptionResolver.getPropertyValues().add("order", 1);
String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);
// 前面更多的是一些web组件的初始化和组装
// 这里统一注册,我们直接看这里就行了
// 所以之前的默认策略并不一定准,因为这里Spring帮我们注入了,所以不会走默认web组件
context.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
context.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
context.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));
context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
context.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExResolverName));
context.registerComponent(new BeanComponentDefinition(statusExceptionResolver, statusExResolverName));
context.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExResolverName));
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(context, source);
context.popAndRegisterContainingComponent();
return null;
}
// 省略其他方法
}
// 之前提到过的26个bean,有相当一部分就是来自这里,我们等下再过
贴上日志,如下
# 上面那个方法又臭又长,为什么要过呢,是因为上面有些web组件bean实现了特定的接口,提前进行了一些不为人知的操作
# 一共找到了26个bean,包含aop:a个、context:b个、mvc:c个、自定义bean:d个
10:34:59.039 [0.1][DEBUG] factory.xml.XmlBeanDefinitionReader 396: Loaded 26 bean definitions from ServletContext resource [/WEB-INF/applicationContext.xml]
10:34:59.039 [0.1][TRACE] factory.support.AbstractBeanDefinitionReader 229: Loaded 26 bean definitions from location pattern [/WEB-INF/applicationContext.xml]
2、小结
mvc标签和aop标签类似,都是向Spring容器注册一些运行的必要bean组件,主要是用于支持web环境,这里隐式注册的bean比较多,共14个,如下
"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" -> {RootBeanDefinition@3283} "Root bean: class [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0" -> {RootBeanDefinition@3324} "Root bean: class [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0" -> {RootBeanDefinition@3326} "Root bean: class [org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"mvcCorsConfigurations" -> {RootBeanDefinition@3474} "Root bean: class [java.util.LinkedHashMap]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"mvcHandlerMappingIntrospector" -> {RootBeanDefinition@3476} "Root bean: class [org.springframework.web.servlet.handler.HandlerMappingIntrospector]; scope=; abstract=false; lazyInit=true; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0" -> {RootBeanDefinition@3328} "Root bean: class [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" -> {RootBeanDefinition@3478} "Root bean: class [org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"mvcContentNegotiationManager" -> {RootBeanDefinition@3480} "Root bean: class [org.springframework.web.accept.ContentNegotiationManagerFactoryBean]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" -> {RootBeanDefinition@3482} "Root bean: class [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" -> {RootBeanDefinition@3318} "Root bean: class [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.web.servlet.handler.MappedInterceptor#0" -> {RootBeanDefinition@3322} "Root bean: class [org.springframework.web.servlet.handler.MappedInterceptor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.format.support.FormattingConversionServiceFactoryBean#0" -> {RootBeanDefinition@3484} "Root bean: class [org.springframework.format.support.FormattingConversionServiceFactoryBean]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" -> {RootBeanDefinition@3486} "Root bean: class [org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"mvcUriComponentsContributor" -> {RootBeanDefinition@3319} "Root bean: class [org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser$CompositeUriComponentsContributorFactoryBean]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
4、context
在正式进入日志分析之前,我们先来大致过一下这个自定义标签的逻辑(虽然可以简单的猜出来)。
1、关键代码
// 首先定位到 ContextNamespaceHandler
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
// 再定位到这一行
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
// 扫描用户包下面的所有类,寻找Spring需要的bean
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
// 注册所需要的bean,注意这里,为什么呢,因为有些bean不是简单的bean,而是系统需要的bean
// 例如被@Configuration、@Component注解标记的bean
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
protected void registerComponents(
XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {
Object source = readerContext.extractSource(element);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {
compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
}
// Register annotation config processors, if necessary.
boolean annotationConfig = true;
if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
}
if (annotationConfig) {
// 注意这里注册了大量的BeanPostProcessor用于支持Spring注解、标准注解
// 如@Bean、@Service、@Component、@Configuration、@Autowired、@Resource等
// 但都不是Spring MVC的注解,如@Controller、@RequestMapping、@ResponseBody等
// Register all relevant annotation post processors
Set<BeanDefinitionHolder> processorDefinitions = AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
}
}
readerContext.fireComponentRegistered(compositeDef);
}
/**
* Register all relevant annotation post processors in the given registry.
* @param registry the registry to operate on
* @param source the configuration source element (already extracted)
* that this registration was triggered from. May be {@code null}.
* @return a Set of BeanDefinitionHolders, containing all bean definitions
* that have actually been registered by this call
*/
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if (beanFactory != null) {
// 用于支持@Order,以定义注入bean的优先级
if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}
// 用于支持@Lazy,以让bean初始化延迟到第一次被用户使用
if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
}
}
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
// 支持@Configuration
// 注意和上面的区别,上面不是以BeanPostProcessor的形式注入,而是以扩展的形式set进DefaultListableBeanFactory
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// 支持@Autowired
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
// 支持@PostConstruct、@PreDestroy、@Resource
if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
try {
def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
AnnotationConfigUtils.class.getClassLoader()));
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
}
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// 支持@EventListener
if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
}
return beanDefs;
}
里面的具体实现逻辑我们就不细看了,贴上日志
# 到这里为止,Spring开始下狠手了,但是这里为什么都不给个明确的提示呢?
10:34:58.873 [0.1][TRACE] context.annotation.ClassPathScanningCandidateComponentProvider 212: JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning
10:34:58.882 [0.1][TRACE] io.support.PathMatchingResourcePatternResolver 323: Resolved classpath location [xiaokui/] to resources [URL [file:/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/]]
# ......省略50行输出......
# 到这里已经完成了对所有用户类的扫描
10:34:58.900 [0.1][TRACE] io.support.PathMatchingResourcePatternResolver 517: Resolved location pattern [classpath*:xiaokui/*/**/*.class] to resources [file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/config/AfterRun.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/config/SpringConfig.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/controller/IndexController.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/controller/LastModifiedCacheController.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/dao/UserDao.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/entity/Apple.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/entity/User.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/service/UserService.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/test/TestLambda.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/test/TestLambda1$A.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/test/TestLambda1$B.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/test/TestLambda1.class]]
# 注意,扫描类已经从 PathMatchingResourcePatternResolver 变成了 ClassPathScanningCandidateComponentProvider,到这里Spring已经在寻找可以注入的Bean了
10:34:58.900 [0.1][TRACE] context.annotation.ClassPathScanningCandidateComponentProvider 426: Scanning file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/config/AfterRun.class]
10:34:58.958 [0.1][DEBUG] context.annotation.ClassPathScanningCandidateComponentProvider 437: Identified candidate component class: file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/config/AfterRun.class]
# ......省略50行输出......
# 继续往下
2、小结
aop和mvc标签解析时,两者都会提前向Spring容器放置一些内置bean,其中有比较关键的bean,例如AnnotationAwareAspectJAutoProxyCreator,也有一些不是那么显眼的bean,例如mvcUriComponentsContributor,但他们两者都有一个特点,就是不会输出日志,只是单纯的把一些必要的BeanDefinition放进IoC容器,然后等待流程往下走。
而context标签的不同点在于,context标签会扫描指定包下的所有类,然后扫描所有@Component注解的类,但为什么@Configuration、@Service、@Controller、@RestController、@Repository、@ControllerAdvice、@RestControllerAdvice注解的类也可以被扫描呢?
你是不是在骗我,欺负我读书读得少?不是的,这是因为上面的那些注解同时都是被@Component注解的,本质上都是@Component注解,是隐式的继承关系。context除了扫描用户自定的bean,还会隐式注入以下5个bean,主要是用户对@Configuration的解析(用于提前配置Spring环境)、@EventListener的解析(用户监听Spring内部事件通知)、@Autowired/@Value/@Resource的解析(用于bean的自动装配)。
"org.springframework.context.annotation.internalConfigurationAnnotationProcessor" -> {RootBeanDefinition@4059} "Root bean: class [org.springframework.context.annotation.ConfigurationClassPostProcessor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.context.event.internalEventListenerFactory" -> {RootBeanDefinition@4062} "Root bean: class [org.springframework.context.event.DefaultEventListenerFactory]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.context.event.internalEventListenerProcessor" -> {RootBeanDefinition@4064} "Root bean: class [org.springframework.context.event.EventListenerMethodProcessor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.context.annotation.internalAutowiredAnnotationProcessor" -> {RootBeanDefinition@4069} "Root bean: class [org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
"org.springframework.context.annotation.internalCommonAnnotationProcessor" -> {RootBeanDefinition@4072} "Root bean: class [org.springframework.context.annotation.CommonAnnotationBeanPostProcessor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"
四、invokeBeanFactoryPostProcessors与@Configuration
1、postProcessBeanFactory
日志如下
# 注册web中特点的域,并为特定web Aware接口赋予特定资源
10:34:59.057 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'request' with implementation [org.springframework.web.context.request.RequestScope@1eea14f9]
10:34:59.058 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'session' with implementation [org.springframework.web.context.request.SessionScope@29022255]
10:34:59.059 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'application' with implementation [org.springframework.web.context.support.ServletContextScope@4913a969]
2、invokeBeanFactoryPostProcessors
先上日志,如下
# 激活各BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor,已配置BeanFactory
# 这里特指ConfigurationClassPostProcessor,来自context标签
# 主要是通过实现BeanFactoryPostProcessor接口的postProcessBeanFactory方法以定制化BeanFactory
10:34:59.098 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
10:34:59.099 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
10:34:59.124 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor' to allow for resolving potential circular references
10:34:59.127 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
# ConfigurationClassPostProcessor里面会触发对@Configuration、@Bean的扫描,并将该Bean以BeanDefinition的形式注册进Spring IoC容器
# 所以@Configuration是先于@Component加载,注意,这里只是注册,并不会实例化,且在这里面可以替换系统默认内置的bean
10:34:59.228 [0.1][TRACE] context.annotation.ConfigurationClassBeanDefinitionReader 284: Registering bean definition for @Bean method xiaokui.config.SpringConfig.userBean()
# 这里触发了增强, ConfigurationClassPostProcessor类同时实现了BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry和BeanFactoryPostProcessor的postProcessBeanFactory方法
# postProcessBeanDefinitionRegistry先于postProcessBeanFactory执行
# postProcessBeanDefinitionRegistry方法用于扫描所有@Configuration中的@Bean,postProcessBeanFactory用于增强所有@Configuration所注解的类
# 为什么需要增强呢?我们后面再来回顾
10:34:59.333 [0.1][TRACE] context.annotation.ConfigurationClassEnhancer 111: Successfully enhanced xiaokui.config.SpringConfig; enhanced class name is: xiaokui.config.SpringConfig$$EnhancerBySpringCGLIB$$91072d89
10:34:59.333 [0.1][TRACE] context.annotation.ConfigurationClassPostProcessor 426: Replacing bean definition 'springConfig' existing class 'xiaokui.config.SpringConfig' with enhanced class 'xiaokui.config.SpringConfig$$EnhancerBySpringCGLIB$$91072d89'
# 另外一个BeanFactoryPostProcessor,来自context标签
# 这里为internalEventListenerProcessor
10:34:59.335 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
10:34:59.336 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.context.event.internalEventListenerProcessor'
10:34:59.338 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.context.event.internalEventListenerProcessor' to allow for resolving potential circular references
10:34:59.338 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.context.event.internalEventListenerProcessor'
# 另外一个BeanFactoryPostProcessor,来自context标签
# 这里为internalEventListenerFactory
10:34:59.339 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
10:34:59.340 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.context.event.internalEventListenerFactory'
10:34:59.340 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.context.event.internalEventListenerFactory' to allow for resolving potential circular references
10:34:59.341 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.context.event.internalEventListenerFactory'
3、为什么需要增强@Configuration类
暂时放下我们的日志追踪,来看一看这一个有趣的问题:为什么需要增强@Configuration注解标记的类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
/**
* Explicitly specify the name of the Spring bean definition associated with the
* {@code @Configuration} class. If left unspecified (the common case), a bean
* name will be automatically generated.
* <p>The custom name applies only if the {@code @Configuration} class is picked
* up via component scanning or supplied directly to an
* {@link AnnotationConfigApplicationContext}. If the {@code @Configuration} class
* is registered as a traditional XML bean definition, the name/id of the bean
* element will take precedence.
* @return the explicit component name, if any (or empty String otherwise)
* @see AnnotationBeanNameGenerator
*/
@AliasFor(annotation = Component.class)
String value() default "";
/**
* Specify whether {@code @Bean} methods should get proxied in order to enforce
* bean lifecycle behavior, e.g. to return shared singleton bean instances even
* in case of direct {@code @Bean} method calls in user code. This feature
* requires method interception, implemented through a runtime-generated CGLIB
* subclass which comes with limitations such as the configuration class and
* its methods not being allowed to declare {@code final}.
* <p>The default is {@code true}, allowing for 'inter-bean references' within
* the configuration class as well as for external calls to this configuration's
* {@code @Bean} methods, e.g. from another configuration class. If this is not
* needed since each of this particular configuration's {@code @Bean} methods
* is self-contained and designed as a plain factory method for container use,
* switch this flag to {@code false} in order to avoid CGLIB subclass processing.
* <p>Turning off bean method interception effectively processes {@code @Bean}
* methods individually like when declared on non-{@code @Configuration} classes,
* a.k.a. "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore
* behaviorally equivalent to removing the {@code @Configuration} stereotype.
* @since 5.2
*/
boolean proxyBeanMethods() default true;
}
通过注释,我们大致知道默认代理bean方法的左右是为了内部bean的引用,即“inter-bean references”。具体见下面这个例子
@Component // true false
//@Configuration // true true
public class TestConfiguration {
static class A1 {
}
static class A2 {
}
static class A3 {
A1 a1;
A2 a2;
A3(A1 a1, A2 a2) {
this.a1 = a1;
this.a2 = a2;
}
}
@Bean
public A1 a1() {
return new A1();
}
@Bean A2 a2() {
return new A2();
}
@Bean A3 a3(A1 a1) {
// 如果启用了代理模式,那么会拦截a2,返回a2所对应的bean,否则返回的是一个新对象
// 这一步并不需要显示指定AOP模块,而是隐式代理
return new A3(a1, a2());
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
A1 a1 = (A1) context.getBean("a1");
A2 a2 = (A2) context.getBean("a2");
A3 a3 = (A3) context.getBean("a3");
System.out.print(a1 == a3.a1 );
System.out.println(" " + (a2 == a3.a2));
}
}
4、registerBeanPostProcessors
继续分析日志,如下
# 开始注册BeanPostProcessor
# 对 AutowiredAnnotationBeanPostProcessor 的初始化注册,来自context标签
10:34:59.344 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
10:34:59.345 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
10:34:59.348 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor' to allow for resolving potential circular references
10:34:59.348 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
# 对 CommonAnnotationBeanPostProcessor 的初始化注册,来自context标签
10:34:59.349 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
10:34:59.349 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
10:34:59.354 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor' to allow for resolving potential circular references
10:34:59.355 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
# 对 AnnotationAwareAspectJAutoProxyCreator 的初始化,来自aop标签
10:34:59.355 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.aop.config.internalAutoProxyCreator'
10:34:59.355 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.aop.config.internalAutoProxyCreator'
10:34:59.379 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.aop.config.internalAutoProxyCreator' to allow for resolving potential circular references
# 对于bean属性的填充,populateBean方法中触发applyPropertyValues
# 还记得Spring AOP章节里提到过的3个合并属性?exposeProxy、proxyTargetClass、order属性,由于显示指定了BeanDefinition中的PropertyValues,所以这里需要有相应处理逻辑以日志,已经改动了原有BeanDefinition
10:34:59.392 [0.1][TRACE] io.support.SpringFactoriesLoader 100: Loaded [org.springframework.beans.BeanInfoFactory] names: [org.springframework.beans.ExtendedBeanInfoFactory]
10:34:59.393 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator]
10:34:59.411 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator]
10:34:59.411 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'advisorAdapterRegistry' of type [org.springframework.aop.framework.adapter.AdvisorAdapterRegistry]
10:34:59.412 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'applyCommonInterceptorsFirst' of type [boolean]
10:34:59.413 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'aspectJAdvisorFactory' of type [org.springframework.aop.aspectj.annotation.AspectJAdvisorFactory]
10:34:59.413 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'beanClassLoader' of type [java.lang.ClassLoader]
10:34:59.414 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'beanFactory' of type [org.springframework.beans.factory.BeanFactory]
10:34:59.414 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:34:59.418 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'customTargetSourceCreators' of type [[Lorg.springframework.aop.framework.autoproxy.TargetSourceCreator;]
10:34:59.419 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'exposeProxy' of type [boolean]
10:34:59.419 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'frozen' of type [boolean]
10:34:59.419 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'includePatterns' of type [java.util.List]
10:34:59.420 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'interceptorNames' of type [[Ljava.lang.String;]
10:34:59.420 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'opaque' of type [boolean]
10:34:59.420 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'optimize' of type [boolean]
10:34:59.421 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:34:59.422 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'proxyClassLoader' of type [java.lang.ClassLoader]
10:34:59.422 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'proxyTargetClass' of type [boolean]
10:34:59.452 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.aop.config.internalAutoProxyCreator'
# 至此所有BeanPostProcessor已初始化完成
# 对于wac.refresh方法中的initMessageSource、initApplicationEventMulticaster、onRefresh方法
10:34:59.453 [0.1][TRACE] context.support.AbstractApplicationContext 753: No 'messageSource' bean, using [Empty MessageSource]
10:34:59.456 [0.1][TRACE] context.support.AbstractApplicationContext 776: No 'applicationEventMulticaster' bean, using [SimpleApplicationEventMulticaster]
10:34:59.459 [0.1][DEBUG] context.support.UiApplicationContextUtils 85: Unable to locate ThemeSource with name 'themeSource': using default [org.springframework.ui.context.support.ResourceBundleThemeSource@305226e7]
五、finishBeanFactoryInitialization与Web组件的初始化
紧跟日志,如下
# 对应wac.refresh方法中的 finishBeanFactoryInitialization 方法
# 这一步主要是用于提前初始化所有非lazy-init的bean,后续的getBean直接走缓存即可
# 注意,这里的bean区别之前的初始化的系统bean,之前的bean是用于配置BeanFactory和配置Bean的bean
# 若无特属于说明,均指普通的@Component指定的bean
10:34:59.462 [0.1][TRACE] factory.support.DefaultListableBeanFactory 848: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d:
defining beans [
mvcContentNegotiationManager,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,
mvcCorsConfigurations,
org.springframework.format.support.FormattingConversionServiceFactoryBean#0,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,
mvcUriComponentsContributor,
org.springframework.web.servlet.handler.MappedInterceptor#0,
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
mvcHandlerMappingIntrospector,
org.springframework.aop.config.internalAutoProxyCreator,
afterRun,
mvcConfig,
springConfig,
indexController,
/last,
testController,
testConfiguration,
userDao,
userService,
org.springframework.context.annotation.internalConfigurationAnnotationProcessor,
org.springframework.context.annotation.internalAutowiredAnnotationProcessor,
org.springframework.context.annotation.internalCommonAnnotationProcessor,
org.springframework.context.event.internalEventListenerProcessor,
org.springframework.context.event.internalEventListenerFactory,
org.springframework.web.servlet.handler.MappedInterceptor#1,
userBean,
a];
root of factory hierarchy
我们大致过一下吧。
1、mvcContentNegotiationManager
MVC内容协商管理器。
// 以下如不作特殊说明,则均来自类AnnotationDrivenBeanDefinitionParser,来自mvc标签
// 值得一提的是,ContentNegotiationManagerFactoryBean实现了FactoryBean、ServletContextAware、InitializingBean
// 所以这就有点意思了,且这个对象大量被其他web组件对象引用
private RuntimeBeanReference getContentNegotiationManager(
Element element, @Nullable Object source, ParserContext context) {
RuntimeBeanReference beanRef;
if (element.hasAttribute("content-negotiation-manager")) {
String name = element.getAttribute("content-negotiation-manager");
beanRef = new RuntimeBeanReference(name);
}
else {
RootBeanDefinition factoryBeanDef = new RootBeanDefinition(ContentNegotiationManagerFactoryBean.class);
factoryBeanDef.setSource(source);
factoryBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 注意这一行
factoryBeanDef.getPropertyValues().add("mediaTypes", getDefaultMediaTypes());
String name = CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
context.getReaderContext().getRegistry().registerBeanDefinition(name, factoryBeanDef);
context.registerComponent(new BeanComponentDefinition(factoryBeanDef, name));
beanRef = new RuntimeBeanReference(name);
}
return beanRef;
}
/**
* Invoked by Spring to inject the ServletContext.
*/
// 以下3个方法均来自类ContentNegotiationManagerFactoryBean
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void afterPropertiesSet() {
build();
}
/**
* Actually build the {@link ContentNegotiationManager}.
* @since 5.0
*/
public ContentNegotiationManager build() {
List<ContentNegotiationStrategy> strategies = new ArrayList<>();
if (this.strategies != null) {
strategies.addAll(this.strategies);
}
else {
if (this.favorPathExtension) {
PathExtensionContentNegotiationStrategy strategy;
if (this.servletContext != null && !useRegisteredExtensionsOnly()) {
strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
}
else {
strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
}
strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
if (this.useRegisteredExtensionsOnly != null) {
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
}
strategies.add(strategy);
}
if (this.favorParameter) {
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
strategy.setParameterName(this.parameterName);
if (this.useRegisteredExtensionsOnly != null) {
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
}
else {
strategy.setUseRegisteredExtensionsOnly(true); // backwards compatibility
}
strategies.add(strategy);
}
if (!this.ignoreAcceptHeader) {
strategies.add(new HeaderContentNegotiationStrategy());
}
if (this.defaultNegotiationStrategy != null) {
strategies.add(this.defaultNegotiationStrategy);
}
}
this.contentNegotiationManager = new ContentNegotiationManager(strategies);
return this.contentNegotiationManager;
}
@Override
@Nullable
public ContentNegotiationManager getObject() {
return this.contentNegotiationManager;
}
/**
* Central class to determine requested {@linkplain MediaType media types}
* for a request. This is done by delegating to a list of configured
* {@code ContentNegotiationStrategy} instances.
*
* <p>Also provides methods to look up file extensions for a media type.
* This is done by delegating to the list of configured
* {@code MediaTypeFileExtensionResolver} instances.
*
*/
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
// 省略类属性和方法
}
贴上日志,如下
# 来自类AnnotationDrivenBeanDefinitionParser,来自mvc标签
10:34:59.463 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'mvcContentNegotiationManager'
10:34:59.463 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'mvcContentNegotiationManager'
10:34:59.486 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'mvcContentNegotiationManager' to allow for resolving potential circular references
10:34:59.487 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.accept.ContentNegotiationManagerFactoryBean]
10:34:59.492 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.accept.ContentNegotiationManagerFactoryBean]
# 代码中加入自定义属性
10:34:59.492 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:34:59.492 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'defaultContentType' of type [org.springframework.http.MediaType]
10:34:59.493 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'defaultContentTypeStrategy' of type [org.springframework.web.accept.ContentNegotiationStrategy]
10:34:59.493 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'defaultContentTypes' of type [java.util.List]
10:34:59.493 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'favorParameter' of type [boolean]
10:34:59.494 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'favorPathExtension' of type [boolean]
10:34:59.494 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'ignoreAcceptHeader' of type [boolean]
10:34:59.494 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'ignoreUnknownPathExtensions' of type [boolean]
10:34:59.494 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mediaTypes' of type [java.util.Properties]
10:34:59.495 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'object' of type [org.springframework.web.accept.ContentNegotiationManager]
10:34:59.495 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'objectType' of type [java.lang.Class]
10:34:59.496 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'parameterName' of type [java.lang.String]
10:34:59.496 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'servletContext' of type [javax.servlet.ServletContext]
10:34:59.496 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'singleton' of type [boolean]
10:34:59.497 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'strategies' of type [java.util.List]
10:34:59.497 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useJaf' of type [boolean]
10:34:59.498 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useRegisteredExtensionsOnly' of type [boolean]
10:34:59.521 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'mvcContentNegotiationManager'
10:34:59.528 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'mvcContentNegotiationManager'
10:34:59.529 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
fsdfs
2、RequestMappingHandlerMapping
RequestMappingHandlerMapping主要负责处理@Controller、@RequestMapping。我们先定位下它的注入代码,如下
// 截取自类AnnotationDrivenBeanDefinitionParser的parse方法
RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, context);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("order", 0);
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
if (element.hasAttribute("enable-matrix-variables")) {
Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}
configurePathMatchingProperties(handlerMappingDef, element, context);
readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
// 对应下文中的 mvcCorsConfigurations 处理跨域问题
handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);
再上日志
# 开始初始化 RequestMappingHandlerMapping
10:34:59.529 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
10:34:59.529 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
10:34:59.560 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping' to allow for resolving potential circular references
10:34:59.562 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]
10:34:59.581 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]
# 开始applyPropertyValues
10:34:59.582 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'alwaysUseFullPath' of type [boolean]
10:34:59.582 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'applicationContext' of type [org.springframework.context.ApplicationContext]
10:34:59.582 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'beanName' of type [java.lang.String]
10:34:59.583 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:34:59.584 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'contentNegotiationManager' of type [org.springframework.web.accept.ContentNegotiationManager]
10:34:59.584 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsConfigurationSource' of type [org.springframework.web.cors.CorsConfigurationSource]
10:34:59.585 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsConfigurations' of type [java.util.Map]
10:34:59.585 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsProcessor' of type [org.springframework.web.cors.CorsProcessor]
10:34:59.586 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'defaultHandler' of type [java.lang.Object]
10:34:59.586 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'detectHandlerMethodsInAncestorContexts' of type [boolean]
10:34:59.587 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'embeddedValueResolver' of type [org.springframework.util.StringValueResolver]
10:34:59.587 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'fileExtensions' of type [java.util.List]
10:34:59.588 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'handlerMethodMappingNamingStrategy' of type [org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy]
10:34:59.588 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'handlerMethods' of type [java.util.Map]
10:34:59.589 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'interceptors' of type [[Ljava.lang.Object;]
10:34:59.590 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'namingStrategy' of type [org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy]
10:34:59.590 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:34:59.591 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'pathMatcher' of type [org.springframework.util.PathMatcher]
10:34:59.591 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'pathPrefixes' of type [java.util.Map]
10:34:59.592 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'removeSemicolonContent' of type [boolean]
10:34:59.592 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'servletContext' of type [javax.servlet.ServletContext]
10:34:59.593 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'urlDecode' of type [boolean]
10:34:59.593 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'urlPathHelper' of type [org.springframework.web.util.UrlPathHelper]
10:34:59.594 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useRegisteredSuffixPatternMatch' of type [boolean]
10:34:59.594 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useSuffixPatternMatch' of type [boolean]
10:34:59.594 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useTrailingSlashMatch' of type [boolean]
# 从这里开始,开始注入bean属性了
# 这里的注入并不是普通的注入,更多的是以下这种形式
# 先定义一个RootBeanDefinition
# RootBeanDefinition factoryBeanDef = new RootBeanDefinition(ContentNegotiationManagerFactoryBean.class);
# 然后指定引用上面BeanDefinition的beanName
# RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, context);
# 这里的contentNegotiationManager实际是一个beanName,所以当设置这个属性时,会触发之前BeanDefinition的初始化
# handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
# 之前已经创建了mvcContentNegotiationManager
10:34:59.605 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'mvcContentNegotiationManager'
# 创建mvcCorsConfigurations,处理跨域问题,注意这里的mvcCorsConfigurations其实是一个LinkedHashMap
10:34:59.607 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'mvcCorsConfigurations'
10:34:59.607 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'mvcCorsConfigurations'
10:34:59.608 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'mvcCorsConfigurations' to allow for resolving potential circular references
10:34:59.608 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'mvcCorsConfigurations'
我们紧跟日志,逐个分析由RequestMappingHandlerMapping引发的各子组件bean的初始化,如下
1、mvcCorsConfigurations
这个bean是用来解决跨域问题的,默认为null,声明如下
// 来自类 AnnotationDrivenBeanDefinitionParser parse方法
// 这里默认参数为null
RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);
/**
* Registers a {@code Map<String, CorsConfiguration>} (mapped {@code CorsConfiguration}s)
* under a well-known name unless already registered. The bean definition may be updated
* if a non-null CORS configuration is provided.
* @return a RuntimeBeanReference to this {@code Map<String, CorsConfiguration>} instance
*/
public static RuntimeBeanReference registerCorsConfigurations(
@Nullable Map<String, CorsConfiguration> corsConfigurations,
ParserContext context, @Nullable Object source) {
if (!context.getRegistry().containsBeanDefinition(CORS_CONFIGURATION_BEAN_NAME)) {
// 注意这里用了一个LinkedHashMap作为载体
RootBeanDefinition corsDef = new RootBeanDefinition(LinkedHashMap.class);
corsDef.setSource(source);
corsDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
if (corsConfigurations != null) {
corsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
}
context.getReaderContext().getRegistry().registerBeanDefinition(CORS_CONFIGURATION_BEAN_NAME, corsDef);
context.registerComponent(new BeanComponentDefinition(corsDef, CORS_CONFIGURATION_BEAN_NAME));
}
else if (corsConfigurations != null) {
BeanDefinition corsDef = context.getRegistry().getBeanDefinition(CORS_CONFIGURATION_BEAN_NAME);
corsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
}
return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME);
}
我们通过一段示例代码来演示一下,如下
// 假设现在有这么一个需求,我程序里面的部分对外接口只对特定域开发,那么应该怎么办
// 例如我现在的域为www.xiaokui.site,但是对www.text1.com站内发来的post/get请求我是拒绝的,而对于www.test2.com站内的请求我是允许的
// @CrossOrigin就是比较优雅的解决这个问题的,其本质就是在处理请求时加入了一个CorsConfiguration
// 这里@CrossOrigin并不会影响另外的Controller,默认的Controlller是隐式可以跨域的,虽然浏览器层面可能会有所限制
@CrossOrigin(origins = "http://www.test2.com")
@Controller
@RequestMapping("/")
public class IndexController implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@RequestMapping("/testCors")
@ResponseBody
public String testCors() {
Object obj = applicationContext.getBean("mvcCorsConfigurations");
System.out.println(obj);
System.out.println(obj.getClass());
return obj.toString();
}
}
/**
* Annotation for permitting cross-origin requests on specific handler classes
* and/or handler methods. Processed if an appropriate {@code HandlerMapping}
* is configured.
*
* <p>Both Spring Web MVC and Spring WebFlux support this annotation through the
* {@code RequestMappingHandlerMapping} in their respective modules. The values
* from each type and method level pair of annotations are added to a
* {@link CorsConfiguration} and then default values are applied via
* {@link CorsConfiguration#applyPermitDefaultValues()}.
*
* <p>The rules for combining global and local configuration are generally
* additive -- e.g. all global and all local origins. For those attributes
* where only a single value can be accepted such as {@code allowCredentials}
* and {@code maxAge}, the local overrides the global value.
* See {@link CorsConfiguration#combine(CorsConfiguration)} for more details.
*/
// 可以在类或方法上声明
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
// 省略其他属性
}
2、MappedInterceptor
先上日志,如下
# 日志输出紧跟上面,第二个初始化的bean为MappedInterceptor
10:34:59.610 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'
10:34:59.611 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'
# 又开始了另一个bean的创建,这里有个依赖链式关系,读者可以自己去跟踪源代码
# 主要引发语句为 new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
# 这个bean的作用主要用于类型转换/格式化,例如完成将Date到String的转换,需要时再来重点看下
10:34:59.619 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#7e62a42d'
10:34:59.620 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:34:59.621 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:34:59.622 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0' to allow for resolving potential circular references
# 创建完毕,并调用afterPropertiesSet方法
10:34:59.623 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:34:59.725 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:34:59.733 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#7e62a42d'
# 可以看到这里出错了且必现,但这不是我们关注的点,跳过即可
10:34:59.747 [0.1][TRACE] factory.support.ConstructorResolver 233: Ignoring constructor [public org.springframework.web.servlet.handler.MappedInterceptor(java.lang.String[],java.lang.String[],org.springframework.web.servlet.HandlerInterceptor)] of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0': org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.web.servlet.handler.MappedInterceptor#0': Unsatisfied dependency expressed through constructor parameter 1: Could not convert argument value of type [org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor] to required type [[Ljava.lang.String;]: Failed to convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'java.lang.String[]'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'java.lang.String': no matching editors or conversion strategy found
10:34:59.748 [0.1][TRACE] factory.support.ConstructorResolver 233: Ignoring constructor [public org.springframework.web.servlet.handler.MappedInterceptor(java.lang.String[],java.lang.String[],org.springframework.web.context.request.WebRequestInterceptor)] of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0': org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.web.servlet.handler.MappedInterceptor#0': Unsatisfied dependency expressed through constructor parameter 1: Could not convert argument value of type [org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor] to required type [[Ljava.lang.String;]: Failed to convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'java.lang.String[]'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'java.lang.String': no matching editors or conversion strategy found
10:34:59.749 [0.1][TRACE] springframework.beans.BeanUtils 545: No property editor [org.springframework.web.context.request.WebRequestInterceptorEditor] found for type org.springframework.web.context.request.WebRequestInterceptor according to 'Editor' suffix convention
10:34:59.750 [0.1][TRACE] factory.support.ConstructorResolver 233: Ignoring constructor [public org.springframework.web.servlet.handler.MappedInterceptor(java.lang.String[],org.springframework.web.context.request.WebRequestInterceptor)] of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0': org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.web.servlet.handler.MappedInterceptor#0': Unsatisfied dependency expressed through constructor parameter 1: Could not convert argument value of type [org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor] to required type [org.springframework.web.context.request.WebRequestInterceptor]: Failed to convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'org.springframework.web.context.request.WebRequestInterceptor'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'org.springframework.web.context.request.WebRequestInterceptor': no matching editors or conversion strategy found
10:34:59.751 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.handler.MappedInterceptor#0' to allow for resolving potential circular references
# 完成创建
10:34:59.752 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'
我们再来过一下它的声明,如下
// 还是来自parse方法
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedInterceptorDef.setSource(source);
mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);
context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
/**
* Call the bean name generator for the given bean definition
* and register the bean definition under the generated name.
* @see XmlBeanDefinitionReader#getBeanNameGenerator()
* @see org.springframework.beans.factory.support.BeanNameGenerator#generateBeanName
* @see BeanDefinitionRegistry#registerBeanDefinition
*/
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
String generatedName = generateBeanName(beanDefinition);
getRegistry().registerBeanDefinition(generatedName, beanDefinition);
return generatedName;
}
最后再来过一下MappedInterceptor的用法,如下
/**
* Contains and delegates calls to a {@link HandlerInterceptor} along with
* include (and optionally exclude) path patterns to which the interceptor should apply.
* Also provides matching logic to test if the interceptor applies to a given request path.
*
* <p>A MappedInterceptor can be registered directly with any
* {@link org.springframework.web.servlet.handler.AbstractHandlerMethodMapping}.
* Furthermore, beans of type {@code MappedInterceptor} are automatically detected by
* {@code AbstractHandlerMethodMapping} (including ancestor ApplicationContext's) which
* effectively means the interceptor is registered "globally" with all handler mappings.
*/
// 实现了 HandlerInterceptor 接口
public final class MappedInterceptor implements HandlerInterceptor {
// 省略类属性和方法
}
// 传统MVC还是需要使用这种方式声明注解
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="xiaokui.config.TestInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
// 这种方式还是对于SpringBoot的支持,在Spring MVC不适用
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
}
}
public class TestInterceptor implements HandlerInterceptor {
private Logger log = LogManager.getLogger(this.getClass());
/**
* 如果请求放行(调用Controller之前),则返回true,否则返回false
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("放行请求:" + request.getRequestURI());
sleep(100);
return true;
}
/**
* 请求已被处理完(调用Controller之后,返回逻辑视图之前),调用此方法
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.debug("执行请求完,返回" + response.getStatus());
sleep(100);
}
/**
* 在返回逻辑试图之后(通常用来释放资源),调用此方法
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.debug("关闭连接资源");
sleep(100);
}
}
// 对于<mvc:interceptors>标签的解析类
class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext context) {
context.pushContainingComponent(
new CompositeComponentDefinition(element.getTagName(), context.extractSource(element)));
RuntimeBeanReference pathMatcherRef = null;
if (element.hasAttribute("path-matcher")) {
pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher"));
}
List<Element> interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor");
for (Element interceptor : interceptors) {
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedInterceptorDef.setSource(context.extractSource(interceptor));
mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
ManagedList<String> includePatterns = null;
ManagedList<String> excludePatterns = null;
Object interceptorBean;
if ("interceptor".equals(interceptor.getLocalName())) {
includePatterns = getIncludePatterns(interceptor, "mapping");
excludePatterns = getIncludePatterns(interceptor, "exclude-mapping");
Element beanElem = DomUtils.getChildElementsByTagName(interceptor, "bean", "ref").get(0);
interceptorBean = context.getDelegate().parsePropertySubElement(beanElem, null);
}
else {
interceptorBean = context.getDelegate().parsePropertySubElement(interceptor, null);
}
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, includePatterns);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean);
if (pathMatcherRef != null) {
mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
}
// 注意这一行,值为org.springframework.web.servlet.handler.MappedInterceptor#1
// 所以我们可以像下面那样做,不使用<mvc:interceptors>来完成拦截器的注入
String beanName = context.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
}
context.popAndRegisterContainingComponent();
return null;
}
}
// 用一个bean替换掉xml中的<mvc:interceptors>标签
// 这就是阅读源码的好处,能帮你从另一个维度去看问题,并解决问题
// 为什么后面有个#1呢?这其实是Spring自动对bean加的一个标志,标识特定类型bean的序号,一般由Spring自动生成
@Bean("org.springframework.web.servlet.handler.MappedInterceptor#1")
public MappedInterceptor testInterceptor() {
return new MappedInterceptor(new String[]{"/**"}, new TestInterceptor());
}
/**
* Call the bean name generator for the given bean definition
* and register the bean definition under the generated name.
* @see XmlBeanDefinitionReader#getBeanNameGenerator()
* @see org.springframework.beans.factory.support.BeanNameGenerator#generateBeanName
* @see BeanDefinitionRegistry#registerBeanDefinition
*/
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
String generatedName = generateBeanName(beanDefinition);
getRegistry().registerBeanDefinition(generatedName, beanDefinition);
return generatedName;
}
3、afterPropertiesSet**
先上日志,如下
10:34:59.753 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
# 这一步我们可以大致猜出它的逻辑,扫描@Controller,将相应方法匹配到相应url,并缓存到Map
# 后续url匹配时,直接调用缓存中相应的方法就行了
10:34:59.792 [0.1][TRACE] servlet.handler.AbstractHandlerMethodMapping 284:
x.c.IndexController:
{ /testPage}: testPage()
{ /testStr}: testStr()
{ /testLogin}: testLogin(String,String,HttpServletRequest)
{ /redirect1}: redirect1(String,String,HttpServletRequest)
{ /redirect2}: redirect2(String,String,RedirectAttributes)
{ /redirectTarget}: redirectTarget(String,String,HttpServletRequest)
{ /forwardTarget}: forwardTarget(String,String)
{ /redirect}: redirect(String,String)
{ /forward}: forward(String,String)
{ [/index, /]}: index()
10:34:59.800 [0.1][DEBUG] servlet.handler.AbstractHandlerMethodMapping 351: 10 mappings in 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
# 完成RequestMappingHandlerMapping的初始化
10:34:59.801 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
再来看下代码,如下
// 来自类 RequestMappingHandlerMapping
@Override
public void afterPropertiesSet() {
// 主要是这一行,虽然默认config就是这样的,这里又再次new了一次
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager());
// 我们重点看下这一行
super.afterPropertiesSet();
}
// 以下方法无特殊说明均来自父类 AbstractHandlerMethodMapping
/**
* Detects handler methods at initialization.
* @see #initHandlerMethods
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
// 遍历所有bean,寻找@Controller
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
// 注意这个判断,子类RequestMappingHandlerMapping进行是重写,默认空实现
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
/**
* {@inheritDoc}
* <p>Expects a handler to have either a type-level @{@link Controller}
* annotation or a type-level @{@link RequestMapping} annotation.
*/
// 来自子类 RequestMappingHandlerMapping
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
/**
* Look for handler methods in the specified handler bean.
* @param handler either a bean name or an actual handler instance
* @see #getMappingForMethod
*/
protected void detectHandlerMethods(Object handler) {
// 这里的hander为beanName,即String类型
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
// 如果是cglib生成的子类,那么这里获取的还是父类
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 这行代码又臭又长,但我们需要知道的是到这一步过后,每个@RequestMapping方法都会生成自己的RequestMappingInfo对象
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
// 就是这一行打印了日志中的匹配信息
// 例如 { /testLogin}: testLogin(String,String,HttpServletRequest)
logger.trace(formatMappings(userType, methods));
}
// 这个东西怎么看都有点不伦不类,类似js中的map((value, index) => {})
// 这里的method即Map的key,mapping即value
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
// 注册url以特定方法
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
// 来自子类 RequestMappingHandlerMapping
@Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
super.registerHandlerMethod(handler, method, mapping);
updateConsumesCondition(mapping, method);
}
/**
* Register a handler method and its unique mapping. Invoked at startup for
* each detected handler method.
* @param handler the bean name of the handler or the handler instance
* @param method the method to register
* @param mapping the mapping conditions associated with the handler method
* @throws IllegalStateException if another method was already registered
* under the same mapping
*/
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
// 注意这一行,所以即使@RequestMapping中有多个匹配路径,这里还是会为分别为每个url单独匹配
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
// 这里添加了对于方法级别的跨域的支持
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
// 来自子类
@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
Class<?> beanType = handlerMethod.getBeanType();
CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);
if (typeAnnotation == null && methodAnnotation == null) {
return null;
}
CorsConfiguration config = new CorsConfiguration();
updateCorsConfig(config, typeAnnotation);
updateCorsConfig(config, methodAnnotation);
if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
config.addAllowedMethod(allowedMethod.name());
}
}
return config.applyPermitDefaultValues();
}
日志输出如下
10:34:59.752 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'
10:34:59.753 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
10:34:59.792 [0.1][TRACE] servlet.handler.AbstractHandlerMethodMapping 284:
x.c.IndexController:
{ /testPage}: testPage()
{ /testStr}: testStr()
{ /testLogin}: testLogin(String,String,HttpServletRequest)
{ /redirect1}: redirect1(String,String,HttpServletRequest)
{ /redirect2}: redirect2(String,String,RedirectAttributes)
{ /redirectTarget}: redirectTarget(String,String,HttpServletRequest)
{ /forwardTarget}: forwardTarget(String,String)
{ /redirect}: redirect(String,String)
{ /forward}: forward(String,String)
{ [/index, /]}: index()
10:34:59.800 [0.1][DEBUG] servlet.handler.AbstractHandlerMethodMapping 351: 10 mappings in 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
10:34:59.801 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
没错,就有这么一大堆。
/**
* Creates {@link RequestMappingInfo} instances from type and method-level
* {@link RequestMapping @RequestMapping} annotations in
* {@link Controller @Controller} classes.
*
*/
// 回过头来看下类声明,这个类实现了BeanNameAware、InitializingBean、ApplicationContextAware、ServletContextAware接口
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware {
// 省略其他属性、方法
}
10:34:59.943 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'
10:34:59.948 [0.1][DEBUG] method.annotation.RequestMappingHandlerAdapter 612: ControllerAdvice beans: none
10:34:59.996 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'
10:34:59.997 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'mvcUriComponentsContributor'
10:34:59.997 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'mvcUriComponentsContributor'
10:34:59.998 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'mvcUriComponentsContributor' to allow for resolving potential circular references
10:34:59.998 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#3d6b105d'
10:35:00.001 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'mvcContentNegotiationManager'
10:35:00.002 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#399347ed#1'
10:35:00.002 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:35:00.003 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#399347ed#1'
10:35:00.004 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#7bfa90b5#1'
10:35:00.005 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#7bfa90b5#1'
10:35:00.005 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#6a2d13e7#1'
10:35:00.006 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#6a2d13e7#1'
10:35:00.007 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#36d6eefe#1'
10:35:00.008 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#36d6eefe#1'
10:35:00.009 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#47a9e300#1'
10:35:00.010 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#47a9e300#1'
10:35:00.010 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#6b9c2558#1'
10:35:00.011 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#6b9c2558#1'
10:35:00.012 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#3e51a1f0#1'
10:35:00.014 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#3e51a1f0#1'
10:35:00.014 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#1b45787b#1'
10:35:00.018 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#1b45787b#1'
10:35:00.020 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name '(inner bean)#3d6b105d'
10:35:00.022 [0.1][DEBUG] method.annotation.RequestMappingHandlerAdapter 612: ControllerAdvice beans: none
10:35:00.023 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#3d6b105d'
10:35:00.025 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser$CompositeUriComponentsContributorFactoryBean]
10:35:00.030 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser$CompositeUriComponentsContributorFactoryBean]
10:35:00.030 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.030 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'conversionService' of type [org.springframework.core.convert.ConversionService]
10:35:00.031 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'handlerAdapter' of type [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter]
10:35:00.031 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'object' of type [org.springframework.web.method.support.CompositeUriComponentsContributor]
10:35:00.032 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'objectType' of type [java.lang.Class]
10:35:00.032 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'singleton' of type [boolean]
10:35:00.033 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:35:00.033 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'mvcUriComponentsContributor'
10:35:00.034 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'mvcUriComponentsContributor'
10:35:00.035 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'
10:35:00.035 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0'
10:35:00.035 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0'
10:35:00.039 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0' to allow for resolving potential circular references
10:35:00.039 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'mvcContentNegotiationManager'
10:35:00.040 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver]
10:35:00.063 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver]
10:35:00.063 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'applicationContext' of type [org.springframework.context.ApplicationContext]
10:35:00.064 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'argumentResolvers' of type [org.springframework.web.method.support.HandlerMethodArgumentResolverComposite]
10:35:00.066 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.068 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'contentNegotiationManager' of type [org.springframework.web.accept.ContentNegotiationManager]
10:35:00.069 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'customArgumentResolvers' of type [java.util.List]
10:35:00.069 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'customReturnValueHandlers' of type [java.util.List]
10:35:00.074 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'exceptionHandlerAdviceCache' of type [java.util.Map]
10:35:00.074 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlerClasses' of type [[Ljava.lang.Class;]
10:35:00.077 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlers' of type [java.util.Set]
10:35:00.077 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'messageConverters' of type [java.util.List]
10:35:00.078 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:35:00.078 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'preventResponseCaching' of type [boolean]
10:35:00.078 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'responseBodyAdvice' of type [java.util.List]
10:35:00.079 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'returnValueHandlers' of type [org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite]
10:35:00.079 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'warnLogCategory' of type [java.lang.String]
10:35:00.085 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#7bfa90b5#2'
10:35:00.086 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#7bfa90b5#2'
10:35:00.087 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#6a2d13e7#2'
10:35:00.088 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#6a2d13e7#2'
10:35:00.088 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#36d6eefe#2'
10:35:00.089 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#36d6eefe#2'
10:35:00.090 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#47a9e300#2'
10:35:00.090 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#47a9e300#2'
10:35:00.091 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#6b9c2558#2'
10:35:00.092 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#6b9c2558#2'
10:35:00.092 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#3e51a1f0#2'
10:35:00.096 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#3e51a1f0#2'
10:35:00.097 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#1b45787b#2'
10:35:00.102 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#1b45787b#2'
10:35:00.107 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0'
10:35:00.109 [0.1][DEBUG] method.annotation.ExceptionHandlerExceptionResolver 294: ControllerAdvice beans: none
10:35:00.110 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0'
10:35:00.110 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0'
10:35:00.110 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0'
10:35:00.111 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0' to allow for resolving potential circular references
10:35:00.112 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver]
10:35:00.118 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver]
10:35:00.118 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.119 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlerClasses' of type [[Ljava.lang.Class;]
10:35:00.120 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlers' of type [java.util.Set]
10:35:00.120 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'messageSource' of type [org.springframework.context.MessageSource]
10:35:00.121 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:35:00.121 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'preventResponseCaching' of type [boolean]
10:35:00.122 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'warnLogCategory' of type [java.lang.String]
10:35:00.126 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0'
10:35:00.126 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0'
10:35:00.127 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0'
10:35:00.128 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0' to allow for resolving potential circular references
10:35:00.128 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver]
10:35:00.134 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver]
10:35:00.134 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.134 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlerClasses' of type [[Ljava.lang.Class;]
10:35:00.135 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlers' of type [java.util.Set]
10:35:00.135 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:35:00.136 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'preventResponseCaching' of type [boolean]
10:35:00.136 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'warnLogCategory' of type [java.lang.String]
10:35:00.137 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0'
10:35:00.138 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
10:35:00.138 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
10:35:00.140 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping' to allow for resolving potential circular references
10:35:00.141 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping]
10:35:00.150 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping]
10:35:00.150 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'alwaysUseFullPath' of type [boolean]
10:35:00.151 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'applicationContext' of type [org.springframework.context.ApplicationContext]
10:35:00.151 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'beanName' of type [java.lang.String]
10:35:00.151 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.152 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsConfigurationSource' of type [org.springframework.web.cors.CorsConfigurationSource]
10:35:00.152 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsConfigurations' of type [java.util.Map]
10:35:00.153 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsProcessor' of type [org.springframework.web.cors.CorsProcessor]
10:35:00.153 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'defaultHandler' of type [java.lang.Object]
10:35:00.153 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'detectHandlersInAncestorContexts' of type [boolean]
10:35:00.154 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'handlerMap' of type [java.util.Map]
10:35:00.155 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'interceptors' of type [[Ljava.lang.Object;]
10:35:00.156 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'lazyInitHandlers' of type [boolean]
10:35:00.156 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:35:00.156 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'pathMatcher' of type [org.springframework.util.PathMatcher]
10:35:00.157 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'removeSemicolonContent' of type [boolean]
10:35:00.157 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'rootHandler' of type [java.lang.Object]
10:35:00.157 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'servletContext' of type [javax.servlet.ServletContext]
10:35:00.158 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'urlDecode' of type [boolean]
10:35:00.158 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'urlPathHelper' of type [org.springframework.web.util.UrlPathHelper]
10:35:00.158 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useTrailingSlashMatch' of type [boolean]
10:35:00.159 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'mvcCorsConfigurations'
10:35:00.160 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'
10:35:00.162 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean '/last'
10:35:00.163 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '/last'
10:35:00.164 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean '/last' to allow for resolving potential circular references
10:35:00.165 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '/last'
10:35:00.166 [0.1][TRACE] servlet.handler.AbstractUrlHandlerMapping 368: Mapped [/last] onto '/last'
10:35:00.166 [0.1][DEBUG] servlet.handler.AbstractDetectingUrlHandlerMapping 86: Detected 1 mappings in 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
10:35:00.167 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
10:35:00.167 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter'
10:35:00.168 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter'
10:35:00.168 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter' to allow for resolving potential circular references
10:35:00.168 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter'
10:35:00.169 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter'
10:35:00.169 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter'
10:35:00.170 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter' to allow for resolving potential circular references
10:35:00.170 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter'
10:35:00.170 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'afterRun'
10:35:00.171 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'afterRun'
10:35:00.171 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'afterRun' to allow for resolving potential circular references
10:35:00.173 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'afterRun'
10:35:00.173 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'springConfig'
10:35:00.174 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'springConfig'
10:35:00.174 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'springConfig' to allow for resolving potential circular references
10:35:00.176 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'springConfig'
10:35:00.176 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'indexController'
10:35:00.176 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'indexController'
10:35:00.183 [0.1][TRACE] factory.annotation.InjectionMetadata 100: Registered injected element on class [xiaokui.controller.IndexController]: AutowiredFieldElement for private xiaokui.service.UserService xiaokui.controller.IndexController.userService
10:35:00.183 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'indexController' to allow for resolving potential circular references
10:35:00.184 [0.1][TRACE] factory.annotation.InjectionMetadata 114: Processing injected element of bean 'indexController': AutowiredFieldElement for private xiaokui.service.UserService xiaokui.controller.IndexController.userService
10:35:00.190 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'userService'
10:35:00.190 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'userService'
10:35:00.191 [0.1][TRACE] factory.annotation.InjectionMetadata 100: Registered injected element on class [xiaokui.service.UserService]: AutowiredFieldElement for private xiaokui.dao.UserDao xiaokui.service.UserService.userDao
10:35:00.192 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'userService' to allow for resolving potential circular references
10:35:00.192 [0.1][TRACE] factory.annotation.InjectionMetadata 114: Processing injected element of bean 'userService': AutowiredFieldElement for private xiaokui.dao.UserDao xiaokui.service.UserService.userDao
10:35:00.193 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'userDao'
10:35:00.193 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'userDao'
10:35:00.194 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'userDao' to allow for resolving potential circular references
10:35:00.195 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'userDao'
10:35:00.196 [0.1][TRACE] factory.annotation.AutowiredAnnotationBeanPostProcessor 586: Autowiring by type from bean name 'userService' to bean named 'userDao'
10:35:00.197 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'userService'
10:35:00.198 [0.1][TRACE] factory.annotation.AutowiredAnnotationBeanPostProcessor 586: Autowiring by type from bean name 'indexController' to bean named 'userService'
10:35:00.198 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'indexController'
10:35:00.198 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean '/last'
10:35:00.199 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'userDao'
10:35:00.199 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'userService'
10:35:00.199 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
10:35:00.199 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
10:35:00.199 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
10:35:00.200 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
10:35:00.200 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
10:35:00.200 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'userBean'
10:35:00.200 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'userBean'
10:35:00.201 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'springConfig'
10:35:00.227 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'userBean' to allow for resolving potential circular references
10:35:00.227 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'userBean'
10:35:00.230 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.config.AfterRun
10:35:00.231 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.config.SpringConfig$$EnhancerBySpringCGLIB$$91072d89
10:35:00.231 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.controller.IndexController
10:35:00.234 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.controller.LastModifiedCacheController
10:35:00.234 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.dao.UserDao
10:35:00.235 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.service.UserService
10:35:00.235 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.entity.Apple
10:35:00.238 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: org.apache.catalina.core.ApplicationContextFacade
10:35:00.240 [0.1][TRACE] context.support.AbstractApplicationContext 802: No 'lifecycleProcessor' bean, using [DefaultLifecycleProcessor]
10:35:00.241 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'lifecycleProcessor'
10:35:00.248 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'afterRun'
org.apache.commons.logging.impl.Log4JLogger@16c90e82
class org.apache.commons.logging.impl.Log4JLogger
10:35:00.255 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'servletConfigInitParams'
10:35:00.256 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'servletContextInitParams'
10:35:00.257 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'jndiProperties'
10:35:00.257 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [java:comp/env/spring.liveBeansView.mbeanDomain]
10:35:00.262 [0.1][DEBUG] springframework.jndi.JndiLocatorSupport 102: Converted JNDI name [java:comp/env/spring.liveBeansView.mbeanDomain] not found - trying original name [spring.liveBeansView.mbeanDomain]. javax.naming.NameNotFoundException: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].
10:35:00.264 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [spring.liveBeansView.mbeanDomain]
10:35:00.265 [0.1][DEBUG] springframework.jndi.JndiPropertySource 101: JNDI lookup for name [spring.liveBeansView.mbeanDomain] threw NamingException with message: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].. Returning null.
10:35:00.266 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemProperties'
10:35:00.266 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemEnvironment'
10:35:00.270 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 96: Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
10:35:00.284 [0.1][INFO] web.context.ContextLoader 307: Root WebApplicationContext initialized in 1941 ms
10:35:00.326 [0.1][INFO] web.servlet.FrameworkServlet 525: Initializing Servlet 'dispatcher'
10:35:00.329 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.active' in PropertySource 'servletConfigInitParams'
10:35:00.329 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.active' in PropertySource 'servletContextInitParams'
10:35:00.329 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.active' in PropertySource 'jndiProperties'
10:35:00.330 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [java:comp/env/spring.profiles.active]
10:35:00.330 [0.1][DEBUG] springframework.jndi.JndiLocatorSupport 102: Converted JNDI name [java:comp/env/spring.profiles.active] not found - trying original name [spring.profiles.active]. javax.naming.NameNotFoundException: Name [spring.profiles.active] is not bound in this Context. Unable to find [spring.profiles.active].
10:35:00.331 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [spring.profiles.active]
10:35:00.331 [0.1][DEBUG] springframework.jndi.JndiPropertySource 101: JNDI lookup for name [spring.profiles.active] threw NamingException with message: Name [spring.profiles.active] is not bound in this Context. Unable to find [spring.profiles.active].. Returning null.
10:35:00.331 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.active' in PropertySource 'systemProperties'
10:35:00.332 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.active' in PropertySource 'systemEnvironment'
10:35:00.332 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 96: Could not find key 'spring.profiles.active' in any property source
10:35:00.332 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.default' in PropertySource 'servletConfigInitParams'
10:35:00.332 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.default' in PropertySource 'servletContextInitParams'
10:35:00.333 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.default' in PropertySource 'jndiProperties'
10:35:00.333 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [java:comp/env/spring.profiles.default]
10:35:00.333 [0.1][DEBUG] springframework.jndi.JndiLocatorSupport 102: Converted JNDI name [java:comp/env/spring.profiles.default] not found - trying original name [spring.profiles.default]. javax.naming.NameNotFoundException: Name [spring.profiles.default] is not bound in this Context. Unable to find [spring.profiles.default].
10:35:00.333 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [spring.profiles.default]
10:35:00.335 [0.1][DEBUG] springframework.jndi.JndiPropertySource 101: JNDI lookup for name [spring.profiles.default] threw NamingException with message: Name [spring.profiles.default] is not bound in this Context. Unable to find [spring.profiles.default].. Returning null.
10:35:00.335 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.default' in PropertySource 'systemProperties'
10:35:00.336 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.default' in PropertySource 'systemEnvironment'
10:35:00.336 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 96: Could not find key 'spring.profiles.default' in any property source
10:35:00.338 [0.1][TRACE] context.support.AbstractApplicationContext 592: Refreshing WebApplicationContext for namespace 'dispatcher-servlet', started on Thu May 07 10:35:00 CST 2020, parent: Root WebApplicationContext
10:35:00.344 [0.1][TRACE] factory.xml.XmlBeanDefinitionReader 318: Loading XML bean definitions from ServletContext resource [/WEB-INF/dispatcher-servlet.xml]
10:35:00.346 [0.1][TRACE] factory.xml.DefaultDocumentLoader 74: Using JAXP provider [com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl]
10:35:00.350 [0.1][TRACE] factory.xml.PluggableSchemaResolver 111: Trying to resolve XML entity with public id [null] and system id [http://www.springframework.org/schema/beans/spring-beans.xsd]
10:35:00.350 [0.1][TRACE] factory.xml.PluggableSchemaResolver 154: Loading schema mappings from [META-INF/spring.schemas]
10:35:00.353 [0.1][TRACE] factory.xml.PluggableSchemaResolver 160: Loaded schema mappings: {https://www.springframework.org/schema/jdbc/spring-jdbc.xsd=org/springframework/jdbc/config/spring-jdbc.xsd, https://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop.xsd, https://www.springframework.org/schema/tx/spring-tx-4.3.xsd=org/springframework/transaction/config/spring-tx.xsd......以下省略50行
10:35:00.354 [0.1][TRACE] factory.xml.PluggableSchemaResolver 128: Found XML schema [http://www.springframework.org/schema/beans/spring-beans.xsd] in classpath: org/springframework/beans/factory/xml/spring-beans.xsd
10:35:00.375 [0.1][TRACE] factory.xml.BeanDefinitionParserDelegate 458: Neither XML 'id' nor 'name' specified - using generated bean name [org.springframework.web.servlet.view.InternalResourceViewResolver#0]
10:35:00.376 [0.1][TRACE] springframework.core.SimpleAliasRegistry 82: Alias definition 'org.springframework.web.servlet.view.InternalResourceViewResolver' registered for name 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'
10:35:00.377 [0.1][DEBUG] factory.xml.XmlBeanDefinitionReader 396: Loaded 1 bean definitions from ServletContext resource [/WEB-INF/dispatcher-servlet.xml]
10:35:00.377 [0.1][TRACE] factory.support.AbstractBeanDefinitionReader 229: Loaded 1 bean definitions from location pattern [/WEB-INF/dispatcher-servlet.xml]
10:35:00.377 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'request' with implementation [org.springframework.web.context.request.RequestScope@3c6d1ed]
10:35:00.378 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'session' with implementation [org.springframework.web.context.request.SessionScope@376104e0]
10:35:00.378 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'application' with implementation [org.springframework.web.context.support.ServletContextScope@5eadb738]
10:35:00.381 [0.1][TRACE] context.support.AbstractApplicationContext 753: No 'messageSource' bean, using [Empty MessageSource]
10:35:00.382 [0.1][TRACE] context.support.AbstractApplicationContext 776: No 'applicationEventMulticaster' bean, using [SimpleApplicationEventMulticaster]
10:35:00.382 [0.1][DEBUG] context.support.UiApplicationContextUtils 85: Unable to locate ThemeSource with name 'themeSource': using default [org.springframework.ui.context.support.DelegatingThemeSource@640e0c2c]
10:35:00.383 [0.1][TRACE] factory.support.DefaultListableBeanFactory 848: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1dc24bf8: defining beans [org.springframework.web.servlet.view.InternalResourceViewResolver#0]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d
10:35:00.383 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'
10:35:00.384 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'
10:35:00.393 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0' to allow for resolving potential circular references
10:35:00.394 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.view.InternalResourceViewResolver]
10:35:00.403 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.view.InternalResourceViewResolver]
10:35:00.403 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'alwaysInclude' of type [boolean]
10:35:00.404 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'applicationContext' of type [org.springframework.context.ApplicationContext]
10:35:00.404 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'attributes' of type [java.util.Properties]
10:35:00.404 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'attributesMap' of type [java.util.Map]
10:35:00.405 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'cache' of type [boolean]
10:35:00.405 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'cacheFilter' of type [org.springframework.web.servlet.view.AbstractCachingViewResolver$CacheFilter]
10:35:00.405 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'cacheLimit' of type [int]
10:35:00.406 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'cacheUnresolved' of type [boolean]
10:35:00.406 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.407 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'contentType' of type [java.lang.String]
10:35:00.407 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'exposeContextBeansAsAttributes' of type [boolean]
10:35:00.407 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'exposePathVariables' of type [java.lang.Boolean]
10:35:00.408 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'exposedContextBeanNames' of type [[Ljava.lang.String;]
10:35:00.408 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:35:00.408 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'prefix' of type [java.lang.String]
10:35:00.409 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'redirectContextRelative' of type [boolean]
10:35:00.409 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'redirectHosts' of type [[Ljava.lang.String;]
10:35:00.409 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'redirectHttp10Compatible' of type [boolean]
10:35:00.410 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'requestContextAttribute' of type [java.lang.String]
10:35:00.410 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'servletContext' of type [javax.servlet.ServletContext]
10:35:00.410 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'suffix' of type [java.lang.String]
10:35:00.411 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'viewClass' of type [java.lang.Class]
10:35:00.411 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'viewNames' of type [[Ljava.lang.String;]
10:35:00.415 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'
10:35:00.416 [0.1][TRACE] context.support.AbstractApplicationContext 802: No 'lifecycleProcessor' bean, using [DefaultLifecycleProcessor]
10:35:00.416 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'lifecycleProcessor'
10:35:00.419 [0.1][TRACE] factory.support.DefaultListableBeanFactory 803: No bean named 'multipartResolver' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d: defining beans [org.springframework.aop.config.internalAutoProxyCreator,mvcContentNegotiationManager,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,mvcCorsConfigurations,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,mvcUriComponentsContributor,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,mvcHandlerMappingIntrospector,afterRun,springConfig,indexController,/last,userDao,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,userBean]; root of factory hierarchy
10:35:00.420 [0.1][TRACE] web.servlet.DispatcherServlet 533: No MultipartResolver 'multipartResolver' declared
10:35:00.422 [0.1][TRACE] factory.support.DefaultListableBeanFactory 803: No bean named 'localeResolver' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d: defining beans [org.springframework.aop.config.internalAutoProxyCreator,mvcContentNegotiationManager,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,mvcCorsConfigurations,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,mvcUriComponentsContributor,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,mvcHandlerMappingIntrospector,afterRun,springConfig,indexController,/last,userDao,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,userBean]; root of factory hierarchy
10:35:00.424 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver'
10:35:00.426 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver'
10:35:00.428 [0.1][TRACE] web.servlet.DispatcherServlet 557: No LocaleResolver 'localeResolver': using default [AcceptHeaderLocaleResolver]
10:35:00.430 [0.1][TRACE] factory.support.DefaultListableBeanFactory 803: No bean named 'themeResolver' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d: defining beans [org.springframework.aop.config.internalAutoProxyCreator,mvcContentNegotiationManager,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,mvcCorsConfigurations,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,mvcUriComponentsContributor,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,mvcHandlerMappingIntrospector,afterRun,springConfig,indexController,/last,userDao,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,userBean]; root of factory hierarchy
10:35:00.432 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.theme.FixedThemeResolver'
10:35:00.433 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.theme.FixedThemeResolver'
10:35:00.434 [0.1][TRACE] web.servlet.DispatcherServlet 582: No ThemeResolver 'themeResolver': using default [FixedThemeResolver]
10:35:00.434 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
10:35:00.435 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
10:35:00.436 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'
10:35:00.437 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter'
10:35:00.437 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter'
10:35:00.439 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0'
10:35:00.440 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0'
10:35:00.440 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0'
10:35:00.440 [0.1][TRACE] factory.support.DefaultListableBeanFactory 803: No bean named 'viewNameTranslator' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d: defining beans [org.springframework.aop.config.internalAutoProxyCreator,mvcContentNegotiationManager,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,mvcCorsConfigurations,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,mvcUriComponentsContributor,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,mvcHandlerMappingIntrospector,afterRun,springConfig,indexController,/last,userDao,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,userBean]; root of factory hierarchy
10:35:00.441 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator'
10:35:00.442 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator'
10:35:00.443 [0.1][TRACE] web.servlet.DispatcherServlet 725: No RequestToViewNameTranslator 'viewNameTranslator': using default [DefaultRequestToViewNameTranslator]
10:35:00.443 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'
10:35:00.447 [0.1][TRACE] factory.support.DefaultListableBeanFactory 803: No bean named 'flashMapManager' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d: defining beans [org.springframework.aop.config.internalAutoProxyCreator,mvcContentNegotiationManager,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,mvcCorsConfigurations,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,mvcUriComponentsContributor,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,mvcHandlerMappingIntrospector,afterRun,springConfig,indexController,/last,userDao,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,userBean]; root of factory hierarchy
10:35:00.452 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.support.SessionFlashMapManager'
10:35:00.453 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.support.SessionFlashMapManager'
10:35:00.453 [0.1][TRACE] web.servlet.DispatcherServlet 789: No FlashMapManager 'flashMapManager': using default [SessionFlashMapManager]
org.apache.commons.logging.impl.Log4JLogger@16c90e82
class org.apache.commons.logging.impl.Log4JLogger
10:35:00.454 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'servletConfigInitParams'
10:35:00.454 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'servletContextInitParams'
10:35:00.454 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'jndiProperties'
10:35:00.454 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [java:comp/env/spring.liveBeansView.mbeanDomain]
10:35:00.455 [0.1][DEBUG] springframework.jndi.JndiLocatorSupport 102: Converted JNDI name [java:comp/env/spring.liveBeansView.mbeanDomain] not found - trying original name [spring.liveBeansView.mbeanDomain]. javax.naming.NameNotFoundException: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].
10:35:00.458 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [spring.liveBeansView.mbeanDomain]
10:35:00.459 [0.1][DEBUG] springframework.jndi.JndiPropertySource 101: JNDI lookup for name [spring.liveBeansView.mbeanDomain] threw NamingException with message: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].. Returning null.
10:35:00.460 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemProperties'
10:35:00.461 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemEnvironment'
10:35:00.464 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 96: Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
10:35:00.464 [0.1][DEBUG] web.servlet.FrameworkServlet 542: enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
10:35:00.464 [0.1][INFO] web.servlet.FrameworkServlet 547: Completed initialization in 138 ms
[2020-05-07 10:35:00,522] Artifact spring-web-demo:war exploded: Artifact is deployed successfully
[2020-05-07 10:35:00,523] Artifact spring-web-demo:war exploded: Deploy took 4,500 milliseconds
3、RequestMappingHandlerAdapter
先上日志
# 省略其他日志
16:36:35.583 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'
16:36:35.590 [0.1][DEBUG] method.annotation.RequestMappingHandlerAdapter 612: ControllerAdvice beans: none
16:36:35.674 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'
再上代码
// 来自类 RequestMappingHandlerAdapter
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
// 主要用于处理@ControllerAdvice
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
}
Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
}
if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
if (logger.isDebugEnabled()) {
int modelSize = this.modelAttributeAdviceCache.size();
int binderSize = this.initBinderAdviceCache.size();
int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
" @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
}
}
}
4、其他bean的初始化
剩下bean的初始化没什么好提的了,就先跳过吧,我们下一节着重看下Web中请求的流转。
六、dispatcher-servlet启动流程概览
1、前情回顾
# ServletContextListener监听器成功加载了application.xml后,成功初始化了RootServletContext
# 在ServletContext成功初始化之后,开始了DispacherServlet的初始化,而入口就是DispatchServlet中的init方法中initServletBean代码
# Servelt容器开始再初始化Servlet
16:51:40.525 [0.1][INFO] web.context.ContextLoader 307: Root WebApplicationContext initialized in 79116 ms
16:51:57.683 [0.1][INFO] web.servlet.FrameworkServlet 525: Initializing Servlet 'dispatcher'
关键代码如下
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
// 来自类 HttpServletBean
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
// 省略其他
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
}
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
// 来自类 FrameworkServlet
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
// 来自类 FrameworkServlet
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
// 省略其他
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
}
// 省略其他
public class DispatcherServlet extends FrameworkServlet {
}
中间有一些其他的流程,但大致还是回到了wac.refresh方法。
2、wac.refresh
主要是加载dispatch-servlet.xm中的bean。
3、onFresh
还记得这个方法吗,这里也只是简单提一下。由于之前已经内置许多web组件,所以这里的默认组件可能并不会被启用。
当这一个方法执行完后,也就以为着Spring Web环境已经启动的差不多了。
我们下一节的关注点是一个Web请求到底在Spring MVC中经历了什么。
七、processRequest
还是回到类DispatcherServlet来,前文我们比较详细的分析了Spring MVC中RootApplicationContext、ChildApplicationContext的启动流程,,这次我们详细看下Spring MVC对请求的处理流程。在DispatcherServlet上寻找我们熟悉的doGet、doPost、service方法(不是init方法了),如下
// 三个方法均来自DispatcherServlet的父类FrameworkServlet
/**
* Delegate GET requests to processRequest/doService.
* <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
* with a {@code NoBodyResponse} that just captures the content length.
* @see #doService
* @see #doHead
*/
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate POST requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 记录请求处理耗时
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 一般为null
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 整合request中的Locale
LocaleContext localeContext = buildLocaleContext(request);
// 默认为null
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 简单的将request和response放进类 ServletRequestAttributes中
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// 真正的逻辑处理doService留给了子类DispatcherServlet
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
方法中已经开始了对请求的处理,虽然把细节转移到了doService方法中实现,但是我们不难看出来处理请求前后所做的不少准备与结尾工作,如下
- 为了保证当前线程的LocaleContext以及RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性。
- 根据当前request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程。
- 委托给doService方法进一步处理。
- 请求处理结束后恢复线程到原始状态。
- 请求处理结束后无论成功与否发布时间通知。
1、对Locale的处理
// 一般为null
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 整合request中的Locale
// 这里的Locale代指浏览器环境/用户OS环境,后台可以根据这个可以进行国际化定制
LocaleContext localeContext = buildLocaleContext(request);
// 一般为null
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 简单的将request和response放进类 ServletRequestAttributes中
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// 子类DispatchServlet重写了父类FrameServlet的buildLocaleContext方法
// 注意,虽然上面3个方法是调用父类的,但实际请求发送给的是DispatchServlet,而并非FrameServlet
/**
* Build a LocaleContext for the given request, exposing the request's primary locale as current locale.
* <p>The default implementation uses the dispatcher's LocaleResolver to obtain the current locale,
* which might change during a request.
* @param request current HTTP request
* @return the corresponding LocaleContext
*/
@Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
// 这就是上节我们提到过的 LocaleResolver,默认为AcceptHeaderLocaleResolver
LocaleResolver lr = this.localeResolver;
if (lr instanceof LocaleContextResolver) {
return ((LocaleContextResolver) lr).resolveLocaleContext(request);
}
// 走到这里
else {
// lambda真tm是个sb,就是进不去,cnmb
// 个人不是很喜欢这种语法
return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
}
}
// 进到这里来了
@Override
public Locale resolveLocale(HttpServletRequest request) {
// 默认为null,可以自定义
Locale defaultLocale = getDefaultLocale();
// 如果默认Locale不为null且浏览器端没有指定语言环境
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
// 进一步进tomcat走了一万个方法
// 最后得出requestLocale = zh_CN
Locale requestLocale = request.getLocale();
// 默认为空
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
Locale supportedLocale = findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
}
return (defaultLocale != null ? defaultLocale : requestLocale);
}
LocaleResolver默认实现为
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
2、WebAsyncManager
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 简单的注册属性,暂时还没发现有什么用途
/**
* Obtain the {@link WebAsyncManager} for the current request, or if not
* found, create and associate it with the request.
*/
public static WebAsyncManager getAsyncManager(ServletRequest servletRequest) {
WebAsyncManager asyncManager = null;
Object asyncManagerAttr = servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE);
if (asyncManagerAttr instanceof WebAsyncManager) {
asyncManager = (WebAsyncManager) asyncManagerAttr;
}
if (asyncManager == null) {
asyncManager = new WebAsyncManager();
servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager);
}
return asyncManager;
}
// 暂时还没发现有什么用
/**
* CallableProcessingInterceptor implementation that initializes and resets
* FrameworkServlet's context holders, i.e. LocaleContextHolder and RequestContextHolder.
*/
private class RequestBindingInterceptor implements CallableProcessingInterceptor {
@Override
public <T> void preProcess(NativeWebRequest webRequest, Callable<T> task) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
initContextHolders(request, buildLocaleContext(request),
buildRequestAttributes(request, response, null));
}
}
@Override
public <T> void postProcess(NativeWebRequest webRequest, Callable<T> task, Object concurrentResult) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
resetContextHolders(request, null, null);
}
}
}
3、initContextHolders
private void initContextHolders(HttpServletRequest request,
@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
}
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
}
这个地方初看可能会觉得很奇怪,可仔细一跟源码就会豁然开朗,如下
// RequestContextHolder逻辑一模一样,这里就不列了
public final class LocaleContextHolder {
private static final ThreadLocal<LocaleContext> localeContextHolder =
new NamedThreadLocal<>("LocaleContext");
private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
new NamedInheritableThreadLocal<>("LocaleContext");
// 省略其他
}
/**
* Associate the given LocaleContext with the current thread.
* <p>The given LocaleContext may be a {@link TimeZoneAwareLocaleContext},
* containing a locale with associated time zone information.
* @param localeContext the current LocaleContext,
* or {@code null} to reset the thread-bound context
* @param inheritable whether to expose the LocaleContext as inheritable
* for child threads (using an {@link InheritableThreadLocal})
* @see SimpleLocaleContext
* @see SimpleTimeZoneAwareLocaleContext
*/
public static void setLocaleContext(@Nullable LocaleContext localeContext, boolean inheritable) {
if (localeContext == null) {
resetLocaleContext();
}
else {
if (inheritable) {
inheritableLocaleContextHolder.set(localeContext);
localeContextHolder.remove();
}
else {
localeContextHolder.set(localeContext);
inheritableLocaleContextHolder.remove();
}
}
}
于是我们可以在单个请求中的任何地方调用如下代码以后去request和response,形如
@RequestMapping("/testStr")
@ResponseBody
public String testStr() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
return request + " " + response;
}
八、doService
接下来到了doService方法,如下
// 来自FrameworkServlet子类DispatcherServlet
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
* for the actual dispatching.
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// debug一下请求
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
// 这一段是对jsp中include指令的支持,具体细节这里暂时跳过去
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
// set进了DispatchServlet的WebApplicationContext
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
// 对FlashMap的处理,解决redirect中传递参数失效的问题
// forward不存在参数丢失的问题
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
// 这里才是真正的逻辑调用
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
doService方法中并没有过多的逻辑,除了注入request一些框架组件外(方便后面的处理),还有就是对FlashMap的处理了。如下是一个简单示例
// 目前只发现这一种实现方式,经过多番实践,这个东西并没有什么卵用,建议忘掉
@RequestMapping("/redirect2")
public String redirect2(String username, String password, RedirectAttributes redirectAttributes) {
// 这里传入的参数会出现在重定向后的url
redirectAttributes.addAttribute("username", username);
redirectAttributes.addAttribute("password", password);
return "redirect: /redirectTarget";
}
@RequestMapping("/redirectTarget")
@ResponseBody
public String redirectTarget(String username, String password, HttpServletRequest request) {
return "username=" + username + ",password=" + password;
}
1、doDispatch
doService将真正逻辑处理委托给了doDispatch方法,如下
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 如果是MultiparContent类型的request类型则转换request为MultipartHttpServletRequest类型的request
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 根据request信息寻找对应的Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 如果没有找到对应的handler则通过response反馈错误信息
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 根据当前handler寻找对应的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 拦截器的preHandler方法的调用
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 真正的激活handler并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 视图名称转换应用于需要添加前缀后缀的情况
applyDefaultViewName(processedRequest, mv);
// 应用所有拦截器的postHander方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理请求返回的处理结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
doDispatch方法中展示了Spring请求处理所涉及的主要逻辑,而我们之前设置在request中的各种辅助属性也都有被派上了用场。下面回顾以下逻辑处理的全过程。
2、checkMultipart
对于请求的处理,Spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换request为MultipartHttpServletRequest类型的request。
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
/**
* Convert the request into a multipart request, and make multipart resolver available.
* <p>If no multipart resolver is set, simply use the existing request.
* @param request current HTTP request
* @return the processed request (multipart wrapper if necessary)
* @see MultipartResolver#resolveMultipart
*/
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
// 如果指定了MultipartResolver
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
}
else if (hasMultipartException(request)) {
logger.debug("Multipart resolution previously failed for current request - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}
// 来源于类 StandardServletMultipartResolver
// 一般是根据multipart/form-data
@Override
public boolean isMultipart(HttpServletRequest request) {
// Same check as in Commons FileUpload...
if (!"post".equalsIgnoreCase(request.getMethod())) {
return false;
}
String contentType = request.getContentType();
return StringUtils.startsWithIgnoreCase(contentType, "multipart/");
}
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
// 后面就是对http协议中的文件转换了
/**
* Create a new StandardMultipartHttpServletRequest wrapper for the given request.
* @param request the servlet request to wrap
* @param lazyParsing whether multipart parsing should be triggered lazily on
* first access of multipart files or parameters
* @throws MultipartException if an immediate parsing attempt failed
* @since 3.2.9
*/
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
throws MultipartException {
super(request);
if (!lazyParsing) {
parseRequest(request);
}
}
3、getHandler
// Determine handler for the current request.
// 根据request信息寻找对应的Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 如果没有找到对应的handler则通过response反馈错误信息
noHandlerFound(processedRequest, response);
return;
}
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 注意这里的handlerMappings并不等于3,即并不是默认策略中的3个
// 原因是加载配置文件时,自定义标签mvc已经配置好了需要的bean
// 为RequestMappingHandlerMapping、BeanNameUrlHandlerMapping
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
// 如果找对对应的handler则直接返回,默认优先查RequestMappingHandlerMapping
if (handler != null) {
return handler;
}
}
}
return null;
}
在之前的内容我们提过,在系统启动时Spring会将所有的映射类型的bean注册到this.handlerMappings变量中,所以此方法的目的就是遍历所有的HanderMapping,并调用getHandler方法进行封装处理。
对于BeanNameUrlHandlerMapping的使用需要该beanName以/开头(用于识别),且继承AbstractController(用于使用),关键代码如下
// BeanNameUrlHandlerMapping的上层父类 AbstractHandlerMapping 实现了ApplicationContextAware和ServletContextAware
// 因此在setApplicationContext中会调用initApplicationContext方法,会自动扫描以/开头的bean
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
/**
* Checks name and aliases of the given bean for URLs, starting with "/".
*/
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
}
以RequestMappingHandlerMapping为例查看其getHandler方法。
// getHandler方法还是由父类AbstractHandlerMapping提供
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
* @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 抽象方法,由子类重写
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
// 类RequestMappingHandlerMapping重写了父类方法
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
try {
// 又调回了父类
return super.getHandlerInternal(request);
}
finally {
ProducesRequestCondition.clearMediaTypesAttribute(request);
}
}
1、getHandlerInternal
/**
* Look up a handler method for the given request.
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 截取用于匹配的url有效路径,如请求是${app}/test_url/a,那么这里就是test_url/a
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
// 获取内部读写锁ReentrantReadWriteLock的读锁
// 内部缓存了Controller处理方法与url对映射关系
this.mappingRegistry.acquireReadLock();
try {
// 根据路径寻找Handler
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
// 释放读锁
this.mappingRegistry.releaseReadLock();
}
}
/**
* Return the mapping lookup path for the given request, within the current
* servlet mapping if applicable, else within the web application.
* <p>Detects include request URL if called within a RequestDispatcher include.
* @param request current HTTP request
* @return the lookup path
* @see #getPathWithinServletMapping
* @see #getPathWithinApplication
*/
public String getLookupPathForRequest(HttpServletRequest request) {
// Always use full path within current servlet context?
if (this.alwaysUseFullPath) {
return getPathWithinApplication(request);
}
// Else, use path within current servlet mapping if applicable
// 优先匹配Servlet
String rest = getPathWithinServletMapping(request);
if (!"".equals(rest)) {
return rest;
}
else {
// 再匹配DispatchServlet内部
return getPathWithinApplication(request);
}
}
/**
* Look up the best-matching handler method for the current request.
* If multiple matches are found, the best match is selected.
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
* @return the best-matching handler method, or {@code null} if no match
* @see #handleMatch(Object, String, HttpServletRequest)
* @see #handleNoMatch(Set, String, HttpServletRequest)
*/
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 先查找缓存,自定义标签mvc会事先解析所有请求并存进这里,所以这里一般不为空
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
2、getHandlerExecutionChain
在寻找到Handler后,接着将配置中的对应的拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象。
/**
* Build a {@link HandlerExecutionChain} for the given handler, including
* applicable interceptors.
* <p>The default implementation builds a standard {@link HandlerExecutionChain}
* with the given handler, the handler mapping's common interceptors, and any
* {@link MappedInterceptor MappedInterceptors} matching to the current request URL. Interceptors
* are added in the order they were registered. Subclasses may override this
* in order to extend/rearrange the list of interceptors.
* <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a
* pre-built {@link HandlerExecutionChain}. This method should handle those
* two cases explicitly, either building a new {@link HandlerExecutionChain}
* or extending the existing chain.
* <p>For simply adding an interceptor in a custom subclass, consider calling
* {@code super.getHandlerExecutionChain(handler, request)} and invoking
* {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
* @param handler the resolved handler instance (never {@code null})
* @param request current HTTP request
* @return the HandlerExecutionChain (never {@code null})
* @see #getAdaptedInterceptors()
*/
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
4、noHandlerFound
每个请求都应该对应着一个Hander,因为每个请求都会在后台有相应的逻辑对应,而逻辑的实现就是在Handler中,所以一旦遇到没有找到相应Handler的情况(包含默认),就只能通过response向用户返回错误信息。
/**
* No handler found -> set appropriate HTTP response status.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception if preparing the response failed
*/
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
}
// 默认为false
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
5、getHandlerAdapter
在WebApplicationContext的初始化过程中我们讨论了HanderAdapters的初始化,了解了在默认情况下普通的Web请求会交给SimpleControllerHandlerAdapter去处理。下面我们以SimpleControllerHandlerAdapter为例来分析获取适配器的逻辑。
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
// 这一步只是寻找,判断的规则也很简单,如下
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
// 这里默认会有三个,分别是RequestMappingHandlerAdapter、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
// 来源于类 RequestMappingHandlerAdapter,一般用于处理@RequestMapping注解,与@Controller配合使用
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
* @param handler the handler instance to check
* @return whether or not this adapter can adapt the given handler
*/
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
// 来源于类 HttpRequestHandlerAdapter
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
// 来源于类 SimpleControllerHandlerAdapter,多用于类直接继承子AbstractController
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
更为复杂的逻辑是在后面实现的。
6、LastModified
在研究Spring对缓存处理的功能支持前,我们先了解一个概念:Last-Modified缓存机制。
- 在客户端第一次输入URL时,服务端会返回内容和状态吗200,表示请求成功,同时会添加一个“Last-Modified”的响应头,表示此文件在服务器上的最后更新时间,例如“Last-Modified:Wed, 14 Mar 2020 20:26:22 GMT”。
- 客户端第二次请求此URL时,客户端会向服务器发送请求头"If-Modified-Since",询问服务器该时间之后当前请求内容是否有被修改过,如果服务器端的内容没有变化,则自动返回HTTP 304状态码(只要响应头,内容为空,这样就节省了网络带宽)。
Spring提供了对Last-Modified机制的支持,只需要实现LastModified接口,如下所示
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
@Component("/last")
public class LastModifiedCacheController extends AbstractController implements LastModified {
private long lastModifiedTime;
private static final DateTimeFormatter DTF_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
String str = parseLongTime(lastModifiedTime);
response.setCharacterEncoding("utf-8");
response.getWriter().write("<h2>当前时间:" + str + "<h2>");
return null;
}
@Override
public long getLastModified(HttpServletRequest request) {
if (lastModifiedTime == 0) {
lastModifiedTime = System.currentTimeMillis();
}
// 这一行决定了是否总是返回最新值,注释掉则每次都返回第一次访问的时间,不注释则每次返回最新时间
// lastModifiedTime = System.currentTimeMillis();
return lastModifiedTime;
}
public static String parseLongTime(long longTime) {
Instant instant = Instant.ofEpochMilli(longTime);
LocalDateTime nowTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
return DTF_DATE_TIME.format(nowTime);
}
}
Spring判断是否过期,通过判断请求的“If-Modified-Since”是否大于等于当前的getLastModified方法的时间戳,如果是,则认为没有修改。否则认为修改了返回最新值。
7、HandlerInterceptor的处理
Servlet API定义的servlet过滤器可以在servlet处理每个Web请求的前后分别对它进行前置处理和后置处理。此外,有些时候,你可能只想处理由某些Spring MVC组件处理的Web请求,并在这些处理程序返回的模型属性被传递到视图之前,对他们进行一些操作。
// 前置拦截
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 真正调用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 后置处理
mappedHandler.applyPostHandle(processedRequest, response, mv);
public class TestInterceptor implements HandlerInterceptor {
private Logger log = LogManager.getLogger(this.getClass());
/**
* 如果请求放行(调用Controller之前),则返回true,否则返回false
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("放行请求:" + request.getRequestURI());
sleep(100);
return true;
}
/**
* 请求已被处理完(调用Controller之后,返回逻辑视图之前),调用此方法
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.debug("执行请求完,返回" + response.getStatus());
sleep(100);
}
/**
* 在返回逻辑试图之后(通常用来释放资源),调用此方法
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.debug("关闭连接资源");
sleep(100);
}
}
1、applyPreHandle
/**
* Apply preHandle methods of registered interceptors.
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// Spring会帮我们内置两个HandlerInterceptor,用于实现Web的特定拦截,只有返回true,则会放行请求
// 第一个是CorsInterceptor,用于拦截不允许跨域的请求
// 第二个是ConversionServiceExposingInterceptor,主要用于支持Jsp标签
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
2、applyPostHandle
/**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// 还是前面两个,需要主要的是,后处理的执行顺序是倒过来的,这个与Spring Cloud Gateway很像
// 即前面是123经过拦截器,后面是321流出拦截器
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
8、ha.handle**
handler方法算是Spring MVC中的重中之重,主要分为两个部分,在找到目标方法后,如何调用方法?又怎么处理方法的返回值?这里为了具体化,我简单列几个比较关注的点吧,如下
- 调用方法中参数是如何确定,包括个数、类型?
- @RequestParam、@ResponseBody、@PathVariable等注解如何生效?
- 如何处理返回的非String类型对象?
- 自动注入的是如何进行的?
// Actually invoke the handler.
// 如果是视图页面,则mv不为null,反之则为null
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
public class ModelAndView {
/** View instance or view name String. */
@Nullable
private Object view;
/** Model Map. */
@Nullable
private ModelMap model;
/** Optional HTTP status for the response. */
@Nullable
private HttpStatus status;
/** Indicates whether or not this instance has been cleared with a call to {@link #clear()}. */
private boolean cleared = false;
// 省略其他get/set方法
}
public interface View {
/**
* Name of the {@link HttpServletRequest} attribute that contains the response status code.
* <p>Note: This attribute is not required to be supported by all View implementations.
* @since 3.0
*/
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
/**
* Name of the {@link HttpServletRequest} attribute that contains a Map with path variables.
* The map consists of String-based URI template variable names as keys and their corresponding
* Object-based values -- extracted from segments of the URL and type converted.
* <p>Note: This attribute is not required to be supported by all View implementations.
* @since 3.1
*/
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
/**
* The {@link org.springframework.http.MediaType} selected during content negotiation,
* which may be more specific than the one the View is configured with. For example:
* "application/vnd.example-v1+xml" vs "application/*+xml".
* @since 3.2
*/
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
/**
* Return the content type of the view, if predetermined.
* <p>Can be used to check the view's content type upfront,
* i.e. before an actual rendering attempt.
* @return the content type String (optionally including a character set),
* or {@code null} if not predetermined
*/
@Nullable
default String getContentType() {
return null;
}
/**
* Render the view given the specified model.
* <p>The first step will be preparing the request: In the JSP case, this would mean
* setting model objects as request attributes. The second step will be the actual
* rendering of the view, for example including the JSP via a RequestDispatcher.
* @param model a Map with name Strings as keys and corresponding model
* objects as values (Map can also be {@code null} in case of empty model)
* @param request current HTTP request
* @param response he HTTP response we are building
* @throws Exception if rendering failed
*/
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
*/
// 这里还是以ReqeustMappingHandlerAdapter为例
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
// 我们主要看下这里,这一步之后已经确定入参参数以及需要返回值类型
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
/**
* Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
* if view resolution is required.
* @since 4.2
* @see #createInvocableHandlerMethod(HandlerMethod)
*/
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 这里的参数解析支持有26种,后文简单列下
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// 返回值解析有15种
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 这个mvcContainer很重要
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// 这里主要是一些对于异步的支持
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 我们主要看下这个方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
/**
* Invoke the method and handle the return value through one of the
* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
* @param webRequest the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type (not resolved)
*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 这里已经取得了返回值
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 这一步主要是解析返回值类型
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
/**
* Invoke the method after resolving its argument values in the context of the given request.
* <p>Argument values are commonly resolved through
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
* The {@code providedArgs} parameter however may supply argument values to be used directly,
* i.e. without argument resolution. Examples of provided argument values include a
* {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
* Provided argument values are checked before argument resolvers.
* <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
* resolved arguments.
* @param request the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type, not resolved
* @return the raw value returned by the invoked method
* @throws Exception raised if no suitable argument resolver can be found,
* or if the method raised an exception
* @see #getMethodArgumentValues
* @see #doInvoke
*/
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 这一步过后,Spring MVC已经可以得到了特定位置参数的值,即已经完成了参数的自动注入
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 接下来开始利用参数调用方法,这里返回的是方法结果值
return doInvoke(args);
}
1、入参处理
这里列下之前提到的26种用于解析参数的类,如下
0 = {RequestParamMethodArgumentResolver@6892}
1 = {RequestParamMapMethodArgumentResolver@6893}
2 = {PathVariableMethodArgumentResolver@6894}
3 = {PathVariableMapMethodArgumentResolver@6895}
4 = {MatrixVariableMethodArgumentResolver@6896}
5 = {MatrixVariableMapMethodArgumentResolver@6897}
6 = {ServletModelAttributeMethodProcessor@6898}
7 = {RequestResponseBodyMethodProcessor@6899}
8 = {RequestPartMethodArgumentResolver@6900}
9 = {RequestHeaderMethodArgumentResolver@6901}
10 = {RequestHeaderMapMethodArgumentResolver@6902}
11 = {ServletCookieValueMethodArgumentResolver@6903}
12 = {ExpressionValueMethodArgumentResolver@6904}
13 = {SessionAttributeMethodArgumentResolver@6905}
14 = {RequestAttributeMethodArgumentResolver@6906}
15 = {ServletRequestMethodArgumentResolver@6907}
16 = {ServletResponseMethodArgumentResolver@6908}
17 = {HttpEntityMethodProcessor@6909}
18 = {RedirectAttributesMethodArgumentResolver@6910}
19 = {ModelMethodProcessor@6911}
20 = {MapMethodProcessor@6912}
21 = {ErrorsMethodArgumentResolver@6913}
22 = {SessionStatusMethodArgumentResolver@6914}
23 = {UriComponentsBuilderMethodArgumentResolver@6915}
24 = {RequestParamMethodArgumentResolver@6916}
25 = {ServletModelAttributeMethodProcessor@6917}
大致过一下这些类的定义,会发现Spring默认提供了很多功能,但我们平时常用的就那么一点,这一部分有待研究,暂时跳过,关键代码如下
/**
* Get the method argument values for the current request, checking the provided
* argument values and falling back to the configured argument resolvers.
* <p>The resulting array will be passed into {@link #doInvoke}.
* @since 5.1.2
*/
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// 主要在于这一行
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
/**
* Iterate over registered
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}
* and invoke the one that supports it.
* @throws IllegalArgumentException if no suitable argument resolver is found
*/
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
大致就跟到这里吧,具体的作用就是给予特定参数以特定的资源。
2、返回值处理
这里列下之前提到的15种用于解析方法返回值的类,如下
0 = {ModelAndViewMethodReturnValueHandler@6501}
1 = {ModelMethodProcessor@6502}
2 = {ViewMethodReturnValueHandler@6503}
3 = {ResponseBodyEmitterReturnValueHandler@6504}
4 = {StreamingResponseBodyReturnValueHandler@6505}
5 = {HttpEntityMethodProcessor@6506}
6 = {HttpHeadersReturnValueHandler@6507}
7 = {CallableMethodReturnValueHandler@6508}
8 = {DeferredResultMethodReturnValueHandler@6509}
9 = {AsyncTaskMethodReturnValueHandler@6510}
10 = {ModelAttributeMethodProcessor@6511}
11 = {RequestResponseBodyMethodProcessor@6512}
12 = {ViewNameMethodReturnValueHandler@6513}
13 = {MapMethodProcessor@6514}
14 = {ModelAttributeMethodProcessor@6515}
大概还是跟上面一样吧,Spring默认提供了很多功能,兼顾了绝大部分场景,这里简单列下关键代码,如下
/**
* Invoke the method and handle the return value through one of the
* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
* @param webRequest the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type (not resolved)
*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 这一行是对于返回值的处理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
/**
* Iterate over registered {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} and invoke the one that supports it.
* @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
*/
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
// 这里还是之前的15种,这里会逐个遍历
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
具体细节就跟到这里吧,有需要再来研究。
3、自定义**
1、顶层接口
通过代码,我们知道了上述过程,虽然不知道内部的实现细节。我们照葫芦画瓢,试着去自己实现一个,先上父接口
/**
* Strategy interface for resolving method parameters into argument values in
* the context of a given request.
*
* @author Arjen Poutsma
* @since 3.1
* @see HandlerMethodReturnValueHandler
*/
// 最顶层解析参数的接口
public interface HandlerMethodArgumentResolver {
/**
* Whether the given {@linkplain MethodParameter method parameter} is
* supported by this resolver.
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter;
* {@code false} otherwise
*/
boolean supportsParameter(MethodParameter parameter);
/**
* Resolves a method parameter into an argument value from a given request.
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to {@link #supportsParameter} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null} if not resolvable
* @throws Exception in case of errors with the preparation of argument values
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
/**
* Strategy interface to handle the value returned from the invocation of a
* handler method .
*
* @author Arjen Poutsma
* @since 3.1
* @see HandlerMethodArgumentResolver
*/
// 顶层接口处理返回值
public interface HandlerMethodReturnValueHandler {
/**
* Whether the given {@linkplain MethodParameter method return type} is
* supported by this handler.
* @param returnType the method return type to check
* @return {@code true} if this handler supports the supplied return type;
* {@code false} otherwise
*/
boolean supportsReturnType(MethodParameter returnType);
/**
* Handle the given return value by adding attributes to the model and
* setting a view or setting the
* {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
* to indicate the response has been handled directly.
* @param returnValue the value returned from the handler method
* @param returnType the type of the return value. This type must have
* previously been passed to {@link #supportsReturnType} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @throws Exception if the return value handling results in an error
*/
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
/**
* Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
* @param <T> the converted object type
*/
// 顶层接口转换结果消息,一般用于@ResponseBody的转换
// 而对于@ResponseBody处理的类为 RequestResponseBodyMethodProcessor
// 该类同时实现了 HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler接口
public interface HttpMessageConverter<T> {
/**
* Indicates whether the given class can be read by this converter.
* @param clazz the class to test for readability
* @param mediaType the media type to read (can be {@code null} if not specified);
* typically the value of a {@code Content-Type} header.
* @return {@code true} if readable; {@code false} otherwise
*/
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
/**
* Indicates whether the given class can be written by this converter.
* @param clazz the class to test for writability
* @param mediaType the media type to write (can be {@code null} if not specified);
* typically the value of an {@code Accept} header.
* @return {@code true} if writable; {@code false} otherwise
*/
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
/**
* Return the list of {@link MediaType} objects supported by this converter.
* @return the list of supported media types, potentially an immutable copy
*/
List<MediaType> getSupportedMediaTypes();
/**
* Read an object of the given type from the given input message, and returns it.
* @param clazz the type of object to return. This type must have previously been passed to the
* {@link #canRead canRead} method of this interface, which must have returned {@code true}.
* @param inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
* @throws HttpMessageNotReadableException in case of conversion errors
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
/**
* Write an given object to the given output message.
* @param t the object to write to the output message. The type of this object must have previously been
* passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
* @param contentType the content type to use when writing. May be {@code null} to indicate that the
* default content type of the converter must be used. If not {@code null}, this media type must have
* previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
* returned {@code true}.
* @param outputMessage the message to write to
* @throws IOException in case of I/O errors
* @throws HttpMessageNotWritableException in case of conversion errors
*/
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
2、关键代码
关键代码如下
@RequestMapping("/testParam")
// 如果使用@ResponseBody的话,等于是选择了RequestResponseBodyMethodProcessor来处理返回值
// 这是可能需要自定义一个HttpMessageConverter,以便让RequestResponseBodyMethodProcessor进行对象转换
public User testParam(@CurrentUser User user) {
System.out.println(user);
return user;
}
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(User.class)
&& parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
User user = new User();
user.setId(1);
user.setName("11");
user.setAge(21);
return user;
}
}
public class UserMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
System.out.println("supportsReturnType" + " " + returnType + " " + returnType.getParameterType());
return returnType.getParameterType() == User.class;
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 不渲染视图
mavContainer.setRequestHandled(true);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
System.out.println("handleReturnValue" + returnValue + " " + returnType + " " + mavContainer + " " + webRequest);
response.getWriter().write(returnValue.toString());
response.flushBuffer();
}
}
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="xiaokui.config.CurrentUserMethodArgumentResolver"/>
</mvc:argument-resolvers>
<mvc:return-value-handlers>
<bean class="xiaokui.config.UserMethodReturnValueHandler"/>
</mvc:return-value-handlers>
</mvc:annotation-driven>
3、自定义HttpMessageConverter
@RequestMapping("/testParam")
@ResponseBody
public User testParam(@CurrentUser User user) {
System.out.println(user);
return user;
}
@Component
public class UserMessageConvert implements HttpMessageConverter<User>, ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
RequestMappingHandlerAdapter adapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
if (!adapter.getMessageConverters().contains(this)) {
adapter.getMessageConverters().add(this);
System.out.println("onApplicationEvent成功添加自定义转换器");
}
}
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return clazz == User.class;
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz == User.class;
}
@Override
public List<MediaType> getSupportedMediaTypes() {
// 不能返回null
List<MediaType> list = new ArrayList<MediaType>(2);
list.add(MediaType.TEXT_PLAIN);
return list;
}
@Override
public User read(Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputMessage.getBody()));
StringBuilder s = new StringBuilder();
String temp;
while ((temp = bufferedReader.readLine()) != null) {
s.append(temp);
}
System.out.println(s);
return null;
}
@Override
public void write(User user, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
outputMessage.getBody().write(user.toString().getBytes());
}
}
4、getModelAndView
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
// 是否返回的视图页面,而不是流输出
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
9、applyDefaultViewName
applyDefaultViewName(processedRequest, mv);
/**
* Do we need view name translation?
*/
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
// 如果返回的是视图页面,但有没有指定视图名称,则返回默认视图
if (mv != null && !mv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
mv.setViewName(defaultViewName);
}
}
}
10、processDispatchResult
Spring在这一步对请求结果进行处理,不管最终的结果是流数据、还是视图界面、还是抛出异常,都在这里统一处理。我们先来回顾一下代码,如下
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
// 这里记录一下可能出现的异常
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
// 而针对Error,则直接抛错即可
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 这里调用拦截器中afterCompletion
// 注意这里把异常传进去了
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
// 如果处理请求过程中出现了异常
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 如果返回的是一个页面,那么这一步开始渲染界面
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
1、异常处理
对于找不到的请求url,可以直接报404,但对于正常情况下的服务器处理请求错误,那么就会进到这里,可以决定是否报500还是200。
/**
* Determine an error ModelAndView via the registered HandlerExceptionResolvers.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen at the time of the exception
* (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to
* @throws Exception if no error ModelAndView found
*/
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
// Spring帮我们内置了3个
// 2 = {DefaultHandlerExceptionResolver@5233}
// 1 = {ResponseStatusExceptionResolver@5232}
// 0 = {ExceptionHandlerExceptionResolver@5231}
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
// 如果exMV为null,则说明异常处理器不能处理该异常,不为null则代表以处理
if (exMv != null) {
// 处理为空的特例,只返回状态码
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
else if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
// 否则直接抛出去
throw ex;
}
2、渲染界面
我们看下渲染界面的逻辑,如下
/**
* Render the given ModelAndView.
* <p>This is the last stage in handling a request. It may involve resolving the view by name.
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws ServletException if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
// 方法均来自类 DispatchServlet
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
// 解析loccal,国内一般为zh_CN,可参考前面对Locale的处理
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
// 开始根据视图名解析视图了
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 找不到视图对应的页面
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 开始渲染,输出字节流
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
/**
* Resolve the given view name into a View object (to be rendered).
* <p>The default implementations asks all ViewResolvers of this dispatcher.
* Can be overridden for custom resolution strategies, potentially based on
* specific model attributes or request parameters.
* @param viewName the name of the view to resolve
* @param model the model to be passed to the view
* @param locale the current locale
* @param request current HTTP servlet request
* @return the View object, or {@code null} if none found
* @throws Exception if the view cannot be resolved
* (typically in case of problems creating an actual View object)
* @see ViewResolver#resolveViewName
*/
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
// 注意这里的逻辑,如果有多个视图解析器的话,那么返回第一个视图解析器能够处理的View
// 在有多个视图解析器的情况下,这一步可能会发送意料之外的情况,例如期待a页面被解析器1解析,而却被解析器2解析
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
// 这一步留给了视图解析器很大的空间,我就在这一步中遇到过坑,这一步里面包含的view资源可能存在,也可能不存在,看解析器里面是否做了资源存在性的检查
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
// 这里以类 InternalResourceViewResolver 为例,大致过下
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
// 是否启用缓存,默认为true
if (!isCache()) {
return createView(viewName, locale);
}
else {
// 所以一般走的是缓存,如果有的话
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
// 第一次创建页面
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
// 可能有资源存在性的检查,这里示例代码是没有的
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace(formatKey(cacheKey) + "served from cache");
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
/**
* Prepares the view given the specified model, merging it with static
* attributes and a RequestContext attribute, if necessary.
* Delegates to renderMergedOutputModel for the actual rendering.
* @see #renderMergedOutputModel
*/
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("View " + formatViewName() +
", model " + (model != null ? model : Collections.emptyMap()) +
(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
/**
* Render the internal resource given the specified model.
* This includes setting the model as request attributes.
*/
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
// 这里使用Servlet中标准的RequestDispatcher来进行转发
// Spring并不直接负责文件页面的输出,而是委托给了Servlet容器
rd.forward(request, response);
}
}
3、triggerAfterCompletion
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
// 调用拦截器中的各个afterCompletion
private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, ex);
}
throw ex;
}
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
// 倒序调用,且传入异常信息
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
至此,Spring总算搞一段落了。剩下的计划有二:
- 自己写一个迷你Spring,包含完整Spring框架的整个处理流程,但只保留核心步骤。这个之前也有写过一部分,但后面发现已经不是很有必要了,为什么呢?因为我对Spring已经到这种程度的理解,写一个不是花点时间的问题吗?但说归说,还是要抽个时间来搞的,毕竟纸上得来终觉浅。
- 开始啃Netty源码,现在是2020年8月30日,希望能在本年内完成吧。
总访问次数: 829次, 一般般帅 创建于 2020-04-02, 最后更新于 2020-09-12
欢迎关注微信公众号,第一时间掌握最新动态!